1 / 43

ECN KG 発表

ECN KG 発表. カーネルをハックしてみよう. ネットワークスタックをいぢる. FreeBSD のカーネルコードに printf をいれて処理を追ってみる 今日のターゲットは ICMP です!. まずは QEMU を手に入れよう. 既に QEMU をダウンロードしてありますよね? http://www.ht.sfc.keio.ac.jp/~sada/etc/qemu-0.7.zip 下記媒体でも配布します。 CD-R SD カード コンパクトフラッシュ QEMU を手に入れたら、ローカルの好きな場所においてください. FreeBSD@QEMU の起動.

virote
Download Presentation

ECN KG 発表

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. ECN KG 発表 カーネルをハックしてみよう

  2. ネットワークスタックをいぢる • FreeBSDのカーネルコードにprintfをいれて処理を追ってみる • 今日のターゲットはICMPです!

  3. まずはQEMUを手に入れよう • 既にQEMUをダウンロードしてありますよね? • http://www.ht.sfc.keio.ac.jp/~sada/etc/qemu-0.7.zip • 下記媒体でも配布します。 • CD-R • SDカード • コンパクトフラッシュ • QEMUを手に入れたら、ローカルの好きな場所においてください

  4. FreeBSD@QEMU の起動 • qemu-freebsd.batをダブルクリック! • ユーザはroot、パスワードは・・・ • 下記のようにタイプするとIPアドレスがとれます • dhclient ed0 • 外部とはport 22しか繋がりません。 • qemu-freebsd.batを編集すると、好きなportで通信できます QEMU 仮想ルータ10.0.2.2 FreeBSD10.0.2.16

  5. カーネルのソースコード構成 • どんなファイルがあるか、ざっくりチェックしてみてください。 i386 X86特有 kern 一般的なカーネル net 一般的なネットワーク netinet TCP/IP / usr src sys sys カーネルヘッダ ufs Unixファイルシステム vm カーネルヘッダ

  6. ICMP Echo Request, Reply application application ip_output() rip_input() icmp_input() ether_output() ip_input() arpresolve() ether_input()

  7. データ送信の流れ

  8. ICMP ECHO Requestの送信 • ICMP Echo Request を送信する • ping プログラムで実際にパケットの流れをみてみる • ip_output() • IPヘッダの初期化 • 経路選択、アドレス選択 • フラグメンテーション • ether_output() • フレーム構築 • インタフェースへのキューイング • arpresolve() • arp 解決

  9. 実際にコードを見ながらprintfを入れよう • printfの基本的な使い方は大丈夫ですよね??だめ? • #define DBG(fmt, arg...) printf("%s: "fmt "\n", __FUNCTION__, ## arg) • ↑みたく書くと、後で色々楽です。 • と、いうわけで、取りあえず送信からいきましょ • ip_output関数はsys/netinet/ip_output.cにあります

  10. ip_output()(IPヘッダができるまで) ICMP ip_output(){ …… ip = mtod(m, struct ip *); if ((flags & (IP_FORWARDING|IP_RAWOUTPUT)) == 0) { ip->ip_v = IPVERSION; ip->ip_hl = hlen >> 2; ip->ip_id = ip_newid(); ipstat.ips_localout++; } else { hlen = ip->ip_hl << 2; } (RAW output なので、   ここでは、初期化されない) IP ICMP

  11. ip_output()(経路選択、アドレス選択) ip_output(){ …… 経路キャッシュや経路テーブルをみて宛て先決定 dst = (struct sockaddr_in *)&ro->ro_dst; again: …… 送信元の決定 if (ip->ip_src.s_addr == INADDR_ANY) { /* Interface may have no addresses. */ if (ia != NULL) { ip->ip_src = IA_SIN(ia)->sin_addr; } }

  12. ip_output()(経路選択、アドレス選択) ip_output(){ …… フラグメントの必要がなければ送信 error = (*ifp->if_output)(ifp, m, (struct sockaddr *)dst, ro->ro_rt); goto done; } } • 送信前にIPヘッダのメンバを表示してみよう

  13. IP header 全体の長さ ip_len バージョン ip_v ヘッダ長 ip_hl tos ip_tos 識別子 ip_id フラグ、フラグメントオフセット ip_off プロトコル ip_p ヘッダチェックサム ip_sum TTL ip_ttl 発信元IPアドレス ip_src 宛て先IPアドレス ip_dst データ

  14. 32bitアドレスの表示方法 #define IP_ADDR_FORMAT(addr) \ ((addr) & 0xff), \ (((addr) >> 8) & 0xff), \ (((addr) >> 16) & 0xff), \ (((addr) >> 24) & 0xff) DBG(“ip_src %d.%d.%d.%d ip_dst %d.%d.%d.%d", IP_ADDR_FORMAT(ip->ip_src.s_addr), IP_ADDR_FORMAT(ip->ip_dst.s_addr));

  15. ここでカーネルをコンパイルしてみよう • コンパイル方法 • cd /usr/src/sys/i386/compile/ECN • make kernel • make kernel-install • ぜーーったい、make clean をしないでください! • 一からmakeしようとすると、それだけでこの授業時間が終わります。。。 • 再起動 • shutdown -r now

  16. Pingしてみよう • タイプしてみてください • dhclient ed0 • ping 10.0.2.2 • dmesg すると、出力はどうなりましたか?

  17. spl…関数 • ip_outputでsplnet(), splx()を見ませんでしたか? • FreeBSDでは、割り込み処理に優先度が割り当てています • ネットワーク周りだとこの2つが使われます。splnet(), splimp() • splimpはsplnetより優先度が高く、ネットワークデバイスからの割り込みを禁止する。 • インタフェースキューを操作する際は、splimpが使用される

  18. spl…関数一覧 低 高 別

  19. ether_output() • sys/net/if_ethersubr.c内の関数 • 処理は • フレーム構築 • インタフェースキューイング

  20. ether_output(ARP解決) int ether_output(){ …… case AF_INET: error = arpresolve(ifp, rt0, m, dst, edst); if (error) return (error == EWOULDBLOCK ? 0 : error); type = htons(ETHERTYPE_IP); break; もし、ARPがまだ解決されてなければ、返る (arpresolvがARP解決後、再びether_outputを呼び出す)。 解決されていれば、フレームの構築を行なう。

  21. ether_output(フレーム構築) int ether_output(){ …… M_PREPEND(m, ETHER_HDR_LEN, M_DONTWAIT); if (m == NULL) senderr(ENOBUFS); eh = mtod(m, struct ether_header *); (void)memcpy(&eh->ether_type, &type, sizeof(eh->ether_type)); (void)memcpy(eh->ether_dhost, edst, sizeof (edst)); M_PREPENDでIPヘッダの前に領域を確保後、 ETHERヘッダの中身を埋めていく IP ICMP eth IP ICMP

  22. ether_output_frame(出力へのキューイング) int ether_output_frame(){ …… IFQ_HANDOFF(ifp, m, error); return (error); } 送信キューにデータをキューイングする。 というわけで、イーサヘッダもprintfしよう。

  23. Ether Header 発信元アドレス 送信元アドレス タイプ struct ether_header{ u_char ether_dhost[6]; u_char ether_shost[6]; u_short ether_type; } IPv4の場合typeは0x0800となる

  24. MACアドレスの表示方法は? 以下のような感じで表示させてみよう マクロにしたり、define で書いてもいいかも。 void mac_print(u_char *macaddr) { int n; printf("mac = ["); for (n=0; n<6; n++) printf("%02x ",(u_int)macaddr[n]); printf("]\n"); }

  25. arpresolve • ether_output()でARP解決がまだされていないときに、ARP解決がされます • ARPは大丈夫ですよね。。。。? • 主に2つの関数が使われる • arpresolve • ARPエントリを検索 • なければ、arprequestを呼び出す • arprequest • ARPパケットを作成 • インタフェースへ出力する

  26. arpresolve(ARPエントリの検索) • sys/netinet/if_ether.cにあります int arpresolve(){ …… rt = arplookup(SIN(dst)->sin_addr.s_addr, 1, 0); ……. if ((rt->rt_expire == 0 || rt->rt_expire > time_second) && sdl->sdl_family == AF_LINK && sdl->sdl_alen != 0) { • arplookupでARPエントリを検索後、エントリが妥当かどうか調べる • もし、妥当であればそのARP解決がされている

  27. arpresolve(ARP要求) int arpresolve(){ …… if (rt->rt_expire) { rt->rt_flags &= ~RTF_REJECT; if (la->la_asked == 0 || rt->rt_expire != time_second) { rt->rt_expire = time_second; if (la->la_asked++ < arp_maxtries) { struct in_addr sin = SIN(rt->rt_ifa->ifa_addr)->sin_addr; RT_UNLOCK(rt); arprequest(ifp, &sin, &SIN(dst)->sin_addr, IF_LLADDR(ifp)); • ARPタイマ、ARP回数のチェック設定後、ARP要求送信を行うarprequest()を呼び出す

  28. arprequest(ARP要求パケット構築、送信) static void arprequest(){ ……… if ((m = m_gethdr(M_DONTWAIT, MT_DATA)) == NULL) return; • ARPパケット用mbufを確保 …… ah->ar_pro = htons(ETHERTYPE_IP); ah->ar_hln = ifp->if_addrlen; ah->ar_pln = sizeof(struct in_addr); ……… • パケットの中身を埋める (*ifp->if_output)(ifp, m, &sa, (struct rtentry *)0); • インタフェースへ出力

  29. ARPヘッダ(メンバを色々出力させてみよう) ■if_ether.h で定義されています struct arphdr { u_short ar_hrd; HWアドレスのフォーマット u_char ar_hln; ハードウェアアドレスの長さ u_char ar_pln;プロトコルアドレスの長さ u_short ar_op; ARPパケットの種類 }; struct ether_arp { struct arphdr ea_hdr; ARPの固定サイズヘッダ u_char arp_sha[ETHER_ADDR_LEN]; 送信元MACアドレス u_char arp_spa[4]; 送信元のプロトコルアドレス u_char arp_tha[ETHER_ADDR_LEN]; ターゲットのMACアドレス u_char arp_tpa[4]; ターゲットのプロトコルアドレス }; ether_arp arphdr

  30. ここまで分かると色々できそう? • IPアドレスを詐称 • MACアドレスを詐称 • ARPフラッディング発生 みたいなカーネルが簡単にできそうだね!! でも、よいこのみんなはやっちゃだめだよ!!

  31. ここでまたカーネルをコンパイルしてみよう • コンパイル方法 • cd /usr/src/sys/i386/compile/ECN • make kernel • make kernel-install • ぜーーったい、make clean をしないでください! • 一からmakeしようとすると、それだけでこの授業時間が終わります。。。 • 再起動 • shutdown -r now

  32. カーネルの出力は? • dmesg で確認してみよう

  33. データ受信の流れ

  34. ICMP ECHO Replyの受信 • ICMP Echo Reply を受信する • ping プログラムで実際にパケットの流れをみてみる • ether_input() • etherヘッダの妥当性チェック • 受信キューに入れる • ip_input() • IPパケットの妥当性チェック • 再組み立てやオプション、転送処理 • 振り分け • icmp_input() • メッセージの妥当性チェック • ICMPメッセージに応じた処理

  35. ether_input(フレームのチェック) • sys/net/if_ethersubr.c 内にあります static void ether_input(){ ……… eh = mtod(m, struct ether_header *); etype = ntohs(eh->ether_type); ……… ether_demux(ifp, m); } • フレームの妥当性をチェックする。 • ether_demuxを呼び出しデマルチプレクス。 • ここでも、etherヘッダのメンバを色々表示してみよう

  36. ether_demux(デマルチプレクス) voidether_demux(){ …… m_adj(m, ETHER_HDR_LEN);etherヘッダ削除 switch (ether_type) { case ETHERTYPE_IP: if (ip_fastforward(m)) return; isr = NETISR_IP; break; ……… netisr_dispatch(isr, m); return; • ether_typeにより上位層への割り込みのスケジューリングがされ、適切なキューへキューイング

  37. ip_input(IPパケットの妥当性チェック) • schednetisrにより呼び出される • sys/netinet/ip_input.cにあります。 voidip_input(){ ………… ip = mtod(m, struct ip *); if (ip->ip_v != IPVERSION) { ipstat.ips_badvers++; goto bad; } • 妥当性のチェックを行う • 受信したIPヘッダの中身を表示させよう

  38. ip_input(オプション処理&転送) • voidip_input(){ ………… if (hlen > sizeof (struct ip) && ip_dooptions(m, 0)) return; • IPヘッダのサイズが20より大きい場合、オプション処理を行う LIST_FOREACH(ia, INADDR_HASH(ip->ip_dst.s_addr), ia_hash) { if (IA_SIN(ia)->sin_addr.s_addr == ip->ip_dst.s_addr && (!checkif || ia->ia_ifp == m->m_pkthdr.rcvif)) • アドレスリストをチェックし、自分宛か、転送するべきか判断する。

  39. ip_input(リアセンブリ&振り分け) • voidip_input(){ ………… if (ip->ip_off & (IP_MF | IP_OFFMASK)) { m = ip_reass(m); • オフセットやフラグがあったならば、フラグメントパケット。リアセンブリ処理を行う (*inetsw[ip_protox[ip->ip_p]].pr_input)(m, hlen); return; • プロトコルスイッチにより、適切なトランスポート層へデータを渡す

  40. icmp_input(ICMPの妥当性チェック) • sys/netinet/ip_icmp.c内にあります voidicmp_input(){ ip = mtod(m, struct ip *); m->m_len -= hlen; m->m_data += hlen; icp = mtod(m, struct icmp *); if (in_cksum(m, icmplen)) { • mbufのデータポインタを調整後、icmpヘッダへのポインタを取り出し、妥当性チェック • 受信したICMPヘッダのメンバを色々表示してみよう IP ICMP

  41. icmp_input(メッセージの処理&raw input) voidicmp_input(){ ……… code = icp->icmp_code; switch (icp->icmp_type) { ………. case ICMP_ECHOREPLY: default: break; } raw: rip_input(m, off); return;

  42. ここでまたまたカーネルをコンパイルしてみようここでまたまたカーネルをコンパイルしてみよう • コンパイル方法 • cd /usr/src/sys/i386/compile/ECN • make kernel • make kernel-install • ぜーーったい、make clean をしないでください! • 一からmakeしようとすると、それだけでこの授業時間が終わります。。。 • 再起動 • shutdown -r now

  43. pingの送受信の流れは分かりましたか? • 余力があれば以下の処理を見つけて表示させてみよう • ICMP ECHO Request を受信後、replyを返す所 • arp request受信後、replyを返す所 • フラグメントを発生させて、フラグメント&組み立て処理 • さらに余力があれば、ネットワークスタックとは関係ないですが、、、、 • カーネル起動時のデバイス初期化メッセージを色々変えてみたら楽しいかも (e.g.そんな餌におれは釣られないクマ――等の巨大AAが出てくるなど。個人的に見てみたい人はどうぞ)

More Related