Mendeteksi sensor oleh ISP dengan OONI

Salah satu indikasi adanya diktator adalah sensor terhadap informasi. Dengan melakukan pembodohan seperti menghalangi pendidikan dan akses terhadap informasi, diktator berharap dapat mempertahankan posisinya. Diktator juga cenderung melakukan penyadapan karena ia ingin segalanya berada dalam kendali. Ia perlu melakukan semua ini karena ia tidak akan sanggup bertahan pada persaingan yang bebas, jujur dan adil.

Open Observatory of Network Interference (https://ooni.torproject.org/) adalah sebuah global observation network yang dikembangkan untuk mendeteksi sensor dan penyadapan yang dilakukan oleh ISP. Pengguna melakukan serangkaian pengujian melalui ooniprobe yang menghubungi server ooni-backend. Semakin banyak jumlah pengguna dan semakin sering pengguna melakukan pengujian, maka peneliti jaringan dapat semakin memastikan praktek sensor dan penyadapan yang dilakukan oleh ISP atau pihak lainnya. Laporan hasil pengujian dapat diakses secara terbuka oleh siapa saja di https://ooni.torproject.org/reports/0.1/ (dikelompokkan berdasarkan negara).

Saya akan mulai dengan melakukan instalasi ooniprobe pada Debian Live (yang merupakan versi stable bukan testing). Pada saat tulisan ini dibuat, versi terbaru ooniprobe di wheezy-backport adalah 1.1.1-1. Saya dapat menemukan versi yang lebih baru di PyPI yang sudah mencapai 1.2.2. Oleh sebab itu, saya akan mencoba men-install ooniprobe yang ada di PyPI. Sebelumnya, saya perlu men-download beberapa package yang dibutuhkan terlebih dahulu dahulu dengan membuka root terminal dan memberikan perintah berikut ini:

# apt-get update && apt-get install python-dev python-pip python-dumbnet libpcap libgeoip-dev libffi-dev tor

Setelah ini, saya dapat men-download ooniprobe di PyPI melalui pip dengan perintah seperti berikut ini:

# pip install ooniprobe

Setelah proses instalasi selesai, saya dapat melihat apa saja pengujian yang dapat dilakukan oleh ooniprobe dengan memberikan perintah berikut ini:

# ooniprobe -s

Perintah di atas juga akan membuat file konfigurasi ooniprobe.conf di lokasi ~/.ooni.

Sebelum memulai pengujian, saya akan men-download resources terbaru dengan memberikan perintah berikut ini:

# ooniresources --update-inputs --update-geoip

Pengujian yang dilakukan oleh OONI dideskripsikan dalam file yang disebut sebagai deck. Dengan demikian, sebuah deck adalah kumpulan dari net test. Saya dapat melihat deck bawaan di di lokasi /usr/share/ooni/decks:

# ls /usr/share/ooni/decks
complete.deck  complete_no_root.deck  fast.deck  fast_no_root.deck  mlab.deck

Saya juga membuat deck khusus untuk mewakili negara saya dengan memberikan perintah berikut ini:

# oonideckgen
Unable to lookup the probe IP via traceroute
Looking up your IP address via torproject
Found your IP via a GeoIP service: x.x.x.x
Deck written to /home/user/deck-id/0.0.1-id.user.deck
Run ooniprobe like so:
ooniprobe -i /home/user/deck-id/0.0.1-id-user.deck

Perintah di atas akan menghasilkan folder dengan nama deck-id. id adalah kode negara untuk Indonesia berdasarkan standar ISO 3166-2. oonideckgen memperoleh informasi mengenai negara saya melalui layanan GeoIP.

Sesuai dengan output perintah oonideckgen, saya dapat memulai pengujian dengan memberikan perintah berikut ini:

# ooniprobe -i /home/user/deck-id/0.0.1-id-user.deck

Setelah pengujian selesai dilakukan, ooniprobe akan menghasil file yaml yang berisi informasi hasil untuk masing-masing net test. File ini akan dikirim ke server laporan sehingga dapat diakses oleh publik di lokasi https://ooni.torproject.org/reports/.

Pengujian blocking/dns_consistency akan mendeteksi sensor yang dilakukan melalui DNS. Ini adalah metode sensor yang sederhana dimana ISP mengalihkan request ke URL yang sah menjadi ke lokasi yang berbeda. Pengujian ini bisa menghasilkan false positive karena website modern biasanya memiliki load-balancing yang akan mengalihkan request ke server terdekat sesuai dengan wilayah geografis.

Pengujian blocking/http_requests akan mendeteksi sensor yang dilakukan secara MITM (yang masuk dalam kategori penyadapan). Pengujian ini akan membandingkan request HTTP normal dari komputer dan request HTTP melalui jaringan Tor. Bila blocking/http_requests mendeteksi perbedaan, ia akan menampilkan pesan seperti:

The two body lengths appear to not match
cencorship could be happening
Headers appear to *not* match

Jaringan Tor adalah jaringan ‘bawang’ dimana saya tidak menghubungi server tujuan secara langsung melainkan ‘berputar-putar’ terlebih dahulu ke sesama pengguna Tor. Tujuannya adalah agar ISP tidak mengetahui destinasi pengguna yang sesungguhnya. Kemungkinan sensor dari ISP juga menjadi sangat kecil karena request HTTP versi Tor akan dilakukan oleh komputer lain yang memakai ISP berbeda (sama seperti pada VPN!). Tentu saja ISP lokal masih memiliki peluang melakukan sensor konten bila mereka berhasil memecahkan enkripsi paket yang dilakukan oleh Tor atau men-install backdoor langsung pada komputer pengguna.

Pengujian manipulation/http_invalid_request_line akan berusaha mengirimkan request ilegal dengan harapan proxy penyadap mengembalikan pesan kesalahan. Pada laporan yaml untuk http_invalid_request_line, saya dapat membandingkan nilai received dan send. Bila berbeda, tampering akan bernilai true. Sebagai contoh, pada pengujian yang saya lakukan untuk sebuah ISP lokal, terlihat secara jelas bahwa nilai received berbeda dengan send. Ini menunjukkan bahwa ISP lokal tersebut memiliki proxy yang ‘menyadap’ komunikasi saya.

Pengujian manipulation/http_header_field_manipulation memeriksa apakah header yang dikirim sama persis dengan header yang diterima oleh server; aktifitas penyadapan bisa saja menyebabkan perubahan http header. Sebagai contoh, pada laporan yaml untuk http_header_field_manipulation, saya menemukan bahwa ISP lokal (atau bisa juga router dan modem) yang saya pakai membuang header acCepT-EncODINg karena HTTP header tersebut tidak sampai di server tujuan. Bila dilihat secara positif, ia membuat pengguna terhindar dari serangan seperti CRIME dan BREACH. Bila dilihat secara negatif, ia membuat akses Internet semakin lambat dan konten lebih mudah disadap.

Memakai DNSCrypt Untuk Menghindari DNS Injection

Selama ini saya tidak pernah memakai DNS server dari ISP, melainkan selalu memakai DNS server dari pihak ketiga. Alasannya adalah DNS server dari ISP sangat lambat dan tidak aman. Semua berjalan dengan lancar sampai ketika beberapa hari yang lalu, saya menemukan banyak masalah pada jaringan saya. Kode program yang menulis log kesalahan ke server chat Slack tiba-tiba mengeluh server tidak ditemukan. Saya juga mulai kesulitan mengakses server GitHub (terkadang sukses tapi terkadang gagal). Seperti masalah jaringan lain pada umumnya, saya mulai melakukan troubleshooting dengan memantau packet melalui Wireshark. Hasilnya, saya menemukan respon dari server milik ISP padahal saya tidak mengaksesnya.

Mengapa demikian? Banyak kemungkinan yang bisa saja terjadi di infrastruktur ISP (atau rahasia lainnya yang tidak saya ketahui karena saya bukan pegawai disitu). ISP bisa saja memakai transparant proxy yang melakukan filtering pada seluruh data yang keluar masuk. Tapi, saya akan mulai dengan memeriksa sesuatu yang lebih masuk akal, misalnya memeriksa DNS aktual yang saya pakai melalui situs https://www.dnsleaktest.com. Saya cukup terkejut ketika mendapati laporan bahwa DNS server yang dipakai adalah DNS server milik ISP. Loh, bukankah saya sudah memakai DNS server milik pihak ketiga? Mungkin sekali ISP telah mengubah setiap request DNS pada port UDP 53 menjadi merujuk ke server DNS milik mereka. Jadi, tidak peduli apapun DNS server yang saya tentukan di router, ISP secara diam-diam akan ‘menggantinya’ ke server DNS yang berbeda. Teknik ini sering kali disebut DNS Injection yang diimplementasikan dengan memakai Transparant DNS Proxy.

Salah satu cara untuk menghindari DNS Injection adalah dengan memakai DNSCrypt yang dikembangkan oleh OpenDNS. DNSCrypt memakai protokol DNSCurve yang ditujukan untuk menggantikan protokol DNS (yang sampai sekarang masih dipakai dimana-mana). Protokol ini lebih aman karena request DNS di-enkripsi sehingga upaya untuk memodifikasi respon dari server akan lebih sulit. Saat ini OpenDNS adalah penyedia terbesar untuk layanan DNS yang mendukung DNSCurve.

Saya segera membuka halaman http://dnscrypt.org/dnscrypt-proxy/download untuk men-download source DNSCrypt. Karena memakai sistem operasi Linux UBuntu, saya men-download file dnscrypt-proxy-1.4.0.tar.gz dan men-extract-nya.

DNSCrypt bergantung pada libsodium. Oleh sebab itu, saya perlu men-download source terbaru library tersebut di https://github.com/jedisct1/libsodium/releases. Setelah men-extract-nya, saya akan men-install library tersebut dengan memberikan perintah berikut ini:

# sudo ./configure
# sudo make
# sudo make install
# sudo ldconfig

Untuk memastikan libsodium telah ter-install, saya dapat memeriksanya dengan perintah berikut ini:

# sudo ldconfig -p | grep libsodium

Berikutnya, saya akan berpindah ke lokasi source code DNSCrypt dan men-install-nya dengan memberikan perintah berikut ini:

# sudo ./configure
# sudo make
# sudo make install

Binary DNSCrypt secara otomatis akan ter-install pada lokasi /usr/local/sbin. Untuk menguji apakah DNSCrypt dapat bekerja dengan baik, saya memberikan perintah berikut ini:

# dnscrypt-proxy --resolver-name=opendns --test=0

Agar lebih aman, saya dapat memakai parameter --user sehingga dnscrypt-proxy melakukan chroot() ke user dengan hak akses yang lebih terbatas. Untuk membuat user baru, saya dapat menggunakan perintah berikut ini:

# sudo adduser --disabled-login --no-create-home --system dnscrypt

Untuk menjalankan DNSCrypt sebagai user baru tersebut, saya dapat menggunakan perintah:

# sudo dnscrypt-proxy --resolver-name=opendns --user=dnscrypt --test=0

Berikutnya, saya ingin DNSCrypt dijalankan secara otomatis. Pada Linux UBuntu yang memakai Upstart, saya bisa menambah file baru di /etc/init dengan nama seperti dnscrypt-proxy.conf yang isinya adalah:

# DNSCrypt Proxy

description "DNSCrypt proxy using OpenDNS resolver"

start on net-device-up

exec /usr/local/sbin/dnscrypt-proxy --resolver-name=opendns --user=dnscrypt

respawn

Penggunaan respawn pada script Upstart di atas menyebabkan DNSCrypt proxy akan dijalankan ulang secara otomatis bila terjadi service tersebut mengalami kegagalan.

Sekarang, bila saya me-restart komputer, DNSCrypt akan tetap dijalankan secara otomatis. Saya bisa memeriksanya dengan menggunakan perintah:

# sudo status dnscrypt-proxy
dnscrypt-proxy start/running, process 199

Setelah DNSCrypt dijalankan, ia akan menyediakan layanan DNS biasa pada port 53. Saya perlu men-konfigurasi jaringan agar tidak lagi mengakses server DNS milik siapapun lagi karena pada akhirnya akan dialihkan ke server milik ISP. Saya perlu men-konfigurasi jaringan agar mengakses DNS di 127.0.0.l (localhost) pada port 53. Setelah menerima request di port ini, DNSCrypt akan melakukan koneksi ter-enkripsi ke port 443 milik server OpenDNS dan mengembalikan hasilnya ke pengguna.

Tapi ada satu masalah yang perlu saya selesaikan terlebih dahulu.

Pada versi Ubuntu baru, saya menemukan bahwa Dnsmasq akan dijalankan secara otomatis. Dnsmasq juga akan memakai port 53 pada 127.0.0.1 sebagai layanan DNS. Untuk itu, saya perlu mematikan layanan Dnsmasq karena tidak dibutuhkan lagi. Caranya adalah dengan mengubah isi file /etc/NetworkManager/NetworkManager.conf dan memberikan komentar pada baris berikut ini:

# dns=dnsmasq

Sebagai langkah terakhir, saya perlu mengatur masing-masing jaringan agar memakai DNS server pada lokasi 127.0.0.1, seperti pada gambar berikut ini:

Mengatur DNS untuk jaringan di Linux Ubuntu

Mengatur DNS untuk jaringan di Linux Ubuntu

Setelah me-restart jaringan, DNS injection dari ISP tidak lagi bekerja. Saya bisa memastikan diri memakai DNS resolver dari OpenDNS dan bukan milik ISP dengan membuka halaman http://www.opendns.com/welcome. Bila memakai DNS server dari OpenDNS, akan terlihat tanda centang besar.

Membongkar “Operating System” modem ADSL

Karena kebetulan ada sebuah “modem” router ADSL yang sedang tidak terpakai, timbul rasa ingin tahu bagaimana cara kerja  modem tersebut.  Seperti yang sudah diketahui banyak orang, untuk melakukan konfigurasi modem ADSL bisa melalui web dengan membuka URL 192.168.1.1.  Setelah saya melakukan googling, server web yang dipakai untuk “hosting” halaman konfigurasi modem adalah RomPager produk dari AllegroSoft.   RomPager adalah sebuah web server yang biasa dipakai pada embedded device dan dibuat dengan bahasa pemograman C.

Tapi ternyata melakukan konfigurasi modem tidak hanya bisa melalui web, juga bisa melalui telnet!  Yup, saya bisa membuka command prompt dan memberikan perintah:

telnet 192.168.1.1

Setelah itu, saya akan diminta untuk memasukkan password administrasi.  Sekarang, saya bisa memberikan perintah untuk mengatur modem.  Perintah yang ada jauh lebih banyak  dibandingkan melalui interface web.  Pertanyaannya adalah perintah apa yang dapat saya berikan?

Setelah melakukan googling, saya menemukan bahwa kebanyakan perintah yang dapat saya berikan sangat mirip dengan perintah untuk ZyNOS.  ZyNOS sendiri adalah sebuah sistem operasi yang dikembangkan oleh ZyXEL Communication Corporation untuk perangkat networking.   Sistem operasi ini tidak bersifat open-source, tapi kebanyakan modem Huawei dan TP-Link memiliki perintah yang sangat mirip.

Tidak semua perintah yang ada di daftar perintah ZyNOS dapat dipakai di modem percobaan.  Tetapi  saking banyaknya perintah yang mirip, membuat saya percaya bahwa OS yang dipakai oleh modem saya adalah ZyNOS.

Langkah selanjutnya adalah bagaimana untuk memperoleh backup firmware yang ada?  Saya bisa memperoleh backup ROM dengan mudah melalui interface web, tetapi saya ingin melihat firmware yang berisi seluruh kode program yang menjadi inti dari modem saya. Saya sempat kecewa karena beberapa post di forum mengatakan bahwa model ini tidak dapat di-backup firmware-nya.  Saya tahu bahwa port ftp di 192.168.1.1 menyala, dan saat melakukan ftp ke 192.168.1.1, saya menemukan 2 file, yaitu RAS dan ROM-0.  ROM-0 adalah file ROM yang bisa saya peroleh melalui interface web.  File RAS pastinya berisi firmware, namun saat di-get melalui ftp, muncul pesan kesalahan yang mengatakan bahwa saya tidak memiliki izin untuk mengambil file tersebut.

Beruntung, saya tetap melanjutkan googling dengan keyword berbeda, dan akhirnya menemukan cara untuk mengambil file firmware tersebut.  Saya memberikan perintah ini setelah masuk ke dalam telnet:

sys stdio 0

Setelah itu, saya menggunakan tftp (bukan ftp) untuk mengambil file RAS.  Btw, tftp adalah software di Linux yang memakai Trivial File Transfer Protocol (TFTP).  TFTP berbeda dari FTP dimana TFTP sangat sederhana, misalnya tidak perlu login.

Modem percobaan saya memakai chipset ADSL TrendChip dan tidak banyak informasi yang saya peroleh mengenai chipset ini.  File RAS yang telah saya peroleh terdiri atas beberapa bagian, seperti file gambar GIF,  header ZyNOS yang diikuti dengan bagian yang terkompresi (dengan format LZMA atau 7-zip format).   Setelah memecah file RAS dan melakukan dekompresi, saya bisa melihat isi firmware secara jelas.  Saat ini yang dapat saya baca hanya kode HTML, pesan kesalahan,  sedangkan kode bahasa mesinnya tidak satupun saya pahami 🙂   Tapi dari hasil googling, ada beberapa orang yang nekad melakukan reverse engineering kode yang ada (walaupun ini mungkin melanggar hukum, tergantung negara).   Dunia memang lucu.  Disaat saya bingung memikirkan cari kerja untuk mencukupi kebutuhan makan sehari-hari, ada banyak orang-orang diluar sana yang menghamburkan waktunya melakukan hal-hal unik dan berguna.  Bicara soal makan, sepertinya saya perlu segera memeriksa apakah masih ada indomie yang tersisa tuk mengisi laper setelah berjam-jam di depan komputer 🙂

DNS: Mencari Alamat

DNS query ke name resolver dilakukan dengan mengirim packet UDP melalui port standard 53. Berikut ini adalah contoh isi query DNS:

a9 b2 01 00 00 01 00 00 - 00 00 00 0a 64 65 76 65
6c 6f 70 65 72 73 03 73 - 75 6e 03 63 6f 6d 00 00
01 00 01

Dua byte pertama adalah header ID yang bernilai 0xa9b2. Setelah itu adalah dua byte yang berisi flag, yaitu 0x0100. Jika diubah ke dalam bentuk binary, 0x0100 akan menjadi = 0000 0001 0000 0000. Bit pertama, berisi nilai 0, menunjukkan kalau ini adalah query. Bit ini akan bernilai 1 jika packet ini adalah DNS response. Empat bit berikutnya, 0000, menunjukkan ini adalah standard query (nilai 1 untuk inverse query dan 2 untuk server status request). Bit berikutnya secara berurutan menunjukkan nilai flag AA, TC (Truncation), RD (Recursion Desired), RA (Recursion Available), 3 reserved bit yang tidak dipakai, dan 4 bit yang menunjukkan RCODE. Nilai 0 untuk RCODE menunjukkan tidak terjadi kesalahan.

Dua byte berikutnya menujukkan jumlah “pertanyaan” atau alamat yang akan dicari. Nilainya pada contoh diatas adalah 1. Dua byte berikutnya menunjukkan jumlah “jawaban” yang dikirim. Karena ini adalah packet untuk meminta alamat, bukan menjawab, maka nilainya adalah 0. Dua byte berikutnya adalah menunjukkan jumlah server resource records dan dua byte berikutnya menunjukkan jumlah additional resource records. Kedua nilai tersebut umumnya dipakai pada response.

Dua belas byte pertama di atas merupakan bagian dari header. Setelah header, terdapat bagian yang berisi data. Untuk contoh query di atas, bagian ini berisi alamat yang akan dicari. Pada contoh di atas, alamat yang dicari adalah:

0a = 10 desimal
64 65 76 65 6c 6f 70 65 72 73 = developers

03 = 3 desimal
73 75 6e = sun

03 = 3 desimal
63 6f 6d = com

00

Setiap bagian dari nama diawali dengan sebuah byte yang menunjukkan jumlah karakter untuk bagian tersebut. Nama yang akan dicari diakhir dengan byte 00. Pada contoh di atas, nama terdiri atas tiga bagian, yaitu “developers“, “sun” dan “com“, yang jika digabungkan akan menjadi “developers.sun.com

Dua byte berikutnya berisi informasi Query Type. Pada contoh, nilainya adalah 0x0001 yang menunjukkan ini adalah query untuk network address (contoh query lain misalnya query email, dsb). Setelah itu terdapat dua byte yang menunjukkan class, yang nilainya adalah 0x0001 (internet).

Format packet yang berisi respon DNS tidak jauh berbeda dengan query DNS. Pada bagian answers, respon DNS memiliki tiga field tambahan, yaitu empat byte TTL, dua byte data length, dan data (misalnya, IP address yang untuk alamat yang dicari). Selain itu, setelah bagian answer, terdapat banyak bagian authorative nameservers dan additional records. Kedua bagian tersebut memiliki format yang sama seperti bagian answer.

TCP: Pelengkap IP

Internet Protocol (IP) adalah protokol yang tidak reliable. Jika terjadi kesalahan saat mengirim pesan, ia akan mengabaikan packet dan mengembalikan error ICMP saja. Untuk itu, TCP terlahir pada layer transport sebagai protokol yang dapat di-“andal”-kan (reliable). TCP adalah protokol yang rumit, yang terdiri atas sekumpulan software untuk menyediakan layanan komunikasi. Kali ini aku akan mempelajari bagian yang bisa dilihat dengan mata kepala sendiri dari TCP yaitu TCP Protocol Data Unit (PDU). TCP PDU adalah bagian yang dikirim sebagai packet. Berikut ini adalah contohnya:

xx xx 04 11 00 50 68 91   34 ad 00 00 00 00 70 02
40 00 a9 63 00 00 02 04   05 b4 01 01 04 02

Dua byte pertama menunjukkan informasi source port (dalam contoh adalah, 0x0411 atau port 1041) dan dua byte berikutnya menunjukkan informasi destination port (dalam contoh adalah 0x0050 atau port 80). TCP memanfaatkan port untuk membentuk komunikasi yang reliable. Kombinasi antara alamat IP dan nomor port disebut socket. Beberapa port yang berbeda di sebuah host dapat mengakses sebuah destination port yang sama, berkat penggunaan multiplexing. Sebagai contoh, sebuah server web yang membuka port 80 dapat melayani banyak pengunjung tanpa harus membuka port baru yang berbeda untuk setiap pengunjung.

Empat byte berikutnya adalah nilai sequence number. Nilai 0x689134AD disini dihasilkan secara acak. Jika seseorang dalam jaringan dapat menebak dan melanjutkan sequence number, maka ia dapat saja membajak komunikasi TCP kita (oleh sebab itu nilai sequence number diawali dengan angka acak). Empat byte setelah ini adalah acknowledgment number, yang nilainya adalah nilai sequence number untuk packet berikutnya (pada SYN, ini adalah nilai sequence number ditambah dengan 1).

Nilai acknowledgment number pada contoh adalah 0 karena ini adalah sebuah TCP open (awal dari komunikasi/SYN). Berikut ini adalah contoh jawaban dari host tujuan:

xx xx 00 50 04 11 93 cf  35 e5 68 91 34 ae 70 12
83 2c 9c 71 00 00 02 04  05 b4 01 01 01 04 02

Kali ini source port adalah port 80 dan destination adalah port 1041. Disini, host tujuan memiliki nilai sequence number (atau initial sequence number) tersendiri, yaitu 0x93cf35e5. Sementara nilai acknowledgment numbernya adalah 0x689134ae. Ini menunjukkan ia merespon packet yang aku kirim sebelumnya yang memiliki sequence number 0x689134ad.

Kembali ke TCP yang aku kirim pertama kali, empat byte setelah acknowledgment number memiliki nilai 0x7002, atau dalam binary berupa 0111 0000 0000 0010. Empat bit pertama menunjukkan ukuran header dalam satuan 32-bit (4 byte). Nilai disini adalah 0111 (atau 7 desimal) sehingga ukuran header adalah 7 x 4 byte = 28 byte. Empat bit berikutnya harus bernilai 0 (reserved). Setelah itu, setiap bit menunjukkan informasi flag berikut secara berurutan: CWR, ECE, URG, ACK, PSH, RST, SYN, FIN. Pada contoh, bit SYN bernilai 1, menunjukkan bahwa ini adalah awal dari koneksi. Sementara itu, pada contoh packet yang dikirim oleh server web, flag ACK dan SYN bernilai 1.

Dua byte setelah itu adalah ukuran window (range sequence number yang valid untuk diterima). Setelah itu ada dua byte checksum, dan dua byte urgent pointer. Nilai urgent pointer hanya dipakai jika flag URG bernilai 1. Setelah itu ada 8 byte options atau lebih (selalu kelipatan 8) atau tidak ada sama sekali. Setiap options diawali dengan sebuah byte yang disebut option number (nilai 1 untuk NOP, nilai 2 untuk maximum segment size, dsb), kemudian sebuah byte yang menunjukkan jumlah byte untuk option tersebut, lalu diikuti dengan nilai option jika ada.

Untuk membuat sebuah koneksi dari client ke server, TCP menggunakan three-way handshake. Client akan mengirim packet dengan flag SYN bernilai 1 ke server dan nilai sequence number yang acak, misalnya 0x689134ad. Lalu, server akan merespon dengan mengirim packet dengan flag SYN bernilai 1 dan ACK bernilai 1. Selain itu, packet ini juga harus memiliki acknowledgment number yang sesuai, misalnya pada contoh, 0x689134ae. Packet dari server sendiri memiliki sequence number yang acak, misalnya 0x93cf35e5. Berikutnya, client akan merespon dengan mengirim packet dengan flag ACK bernilai 1 ke server. Berdasarkan pada contoh, packet ini memiliki sequence number 0x689134ae dan acknowledgment number 0x93cf35e6. Koneksi baru terbentuk setelah proses handshake ini. Jika digambarkan, mungkin proses handshake akan terlihat seperti ini:

CLIENT                                   SERVER
  | SYN (SEQ=0x689134ad, ACK=0)  ----->   |
  |                                       |
  |                                       |
  | <------   SYN, ACK (SEQ=0x93cf35e5,   |       
  |                     ACK=0x689134ae)   |   
  |                                       |   
  |                                       |   
  | ACK (SEQ=0x689134ae,  ------------>   |
  |      ACK=0x93cf35e6)                  |

Setelah ini, proses transfer data dapat dilakukan. Misalnya, seperti pada proses berikut (nilai sequence number disini tidak ada hubungannya dengan yang sebelumnya):

CLIENT                                   SERVER
  |                                       |
  | ACK, PUSH (SEQ=0x38affe14,  ------->  |
  |            ACK=0x114c618c)            |
  |                                       |
  |                                       |
  | <--------- ACK (SEQ=0x114c618c,       |
  |                 ACK=0x38affff3)       |
  |                                       |
  |     DATA                              |
  | <--------- ACK (SEQ=0x114c618c,       |   
  |                 ACK=0x38affff3)       |   
  |                                       |   
  | ACK (SEQ=0x38affff3, ---------------> |
  |      ACK=0x114c66f0)                  |
  |                                       |
  |                                       |
  |     DATA                              |
  | <---------- ACK (SEQ=0x114c66f0,      |   
  |                  ACK=0x38affff3)      |   
  |                                       |   
  | ACK (SEQ=0x38affff3, ---------------> |
  |      ACK=0x114c6c54)                  |
  |                                       |
  |     DATA                              |
  | <---------- ACK (SEQ=0x114c6c54,      |
  |                  ACK=0x38affff3)      |

Koneksi TCP secara normal akan berakhir saat salah satu pihak mengirim packet dengan flag FIN aktif yang meminta penerima agar menutup koneksi. Si penerima akan merespon dengan ACK dan FIN ke pengirim agar sang pengirim juga ikut menutup koneksi.

Filter Di WinPcap: Nilai Tambah Sebuah Library

Program PING di tulisan sebelumnya baru sampai mengirim ECHO REQUEST saja. Untuk melengkapinya, aku akan mencoba menambahkan kode untuk membaca ECHO REPLY. Tapi kali ini tidak dengan memakai callback karena ada cara lain yang lebih tepat, yaitu dengan menggunakan fungsi pcap_next_ex() yang men-capture packet sesuai kebutuhan saja. Ini adalah contoh kode program yang aku buat:

int status;
while ((status=pcap_next_ex(handle, &header, &packetData))>=0) {
  if (status==0) break;
  struct ECHO_REPLY *echoReply = (struct ECHO_REPLY*)((void*)packetData);
  printf("Menerima ECHO REPLY dengan sequence %d.\n", echoReply->icmp_sequence);
  printf("Data ECHO:\n");
  for (int i=1; i<=32; i++) {     printf("%02x ", echoReply->icmp_data[i-1]);
    if (i%8==0) printf("\n");
  }
  printf("\n");
}

Fungsi pcap_next_ex() akan mengembalikan nilai 1 jika packet dibaca tanpa masalah, nilai 0 jika timeout, nilai -1 jika terjadi error, dan nilai -2 jika EOF saat membaca offline capture. Setelah membaca sebuah packet, aku menampikan nilai icmp_sequence dan juga data yang disisipkan bersama packet ICMP ECHO tersebut.

Sampai disini, akan ada satu masalah! pcap_next_ex() akan mencapture semua paket yang lalu-lalang di network card, baik yang dikirim keluar maupun dikirim masuk, baik TCP, UDP maupun ICMP. Padahal kode program di atas langsung mengandaikan bahwa packet yang diterima adalah packet ICMP ECHO REPLY dari IP tertentu yang dikirim ke komputer ini. Di saat-saat seperti ini, fitur filter milik WinPcap sangat dibutuhkan. Fitur ini dapat menyaring packet apa saja yang akan diterima oleh aplikasi berdasarkan ekspresi boolean yang aku tentukan lewat kode program. Untuk itu, aku harus melibatkan dua fungsi, yaitu pcap_compile() dan pcap_setfilter(). Fungsi pcap_compile() akan menerjemahkan ekspresi dalam bentuk teks ke dalam byte code, kemudian pcap_setfilter() akan menerapkan byte code tersebut. Berikut ini adalah contoh penggunaan pcap_compile():

if (pcap_compile(handle, &filterCode,
  "icmp[icmptype] = 0 and icmp[icmpcode] = 0 and src host 192.168.1.1", 1, 0) < 0) {
  printf("ERROR Compiling Filter\n");
  pcap_close(handle);
  exit(EXIT_FAILURE);
}

filterCode disini adalah sebuah struct bpf_program yang merepresentasikan filter yang sesungguhnya. Parameter terakhir seharusnya berupa network mask (seperti 0xffffff untuk 255.255.255.0). Nilai ini hanya dibutuhkan untuk menyaring packet broadcast, dan karena aku tidak membutuhkan packet broadcast maka aku mengabaikan nilai ini. Lalu ekspresi untuk filter dengan jelas menyatakan untuk hanya menampilkan packet ICMP dengan field icmptype = 0 dan icmpcode = 0 (atau dengan kata lain, ECHO REPLY), dan dengan source address dari 192.168.1.1.

Setelah melakukan proses compile, aku dapat menerapkan filter seperti pada kode berikut:

if (pcap_setfilter(handle, &filterCode) < 0) {
  printf("ERROR Setting Filter\n");
  pcap_close(handle);
  exit(EXIT_FAILURE);
}

Setelah menambahkan fungsi pcap_compile() dan pcap_setfilter() sebelum pcap_next_ex(), akhirnya program PING sederhana ini selesai juga. Saatnya beralih ke topik berikutnya.

Mengirim Raw Packet: Latihan Ping

Setelah sebelumnya aku belajar membaca raw packet yang diterima network card, kali ini aku ingin mencoba mengirim raw packet secara langsung. Keuntungannya adalah aku bisa bebas menentukan semua parameter packet yang aku kirim. Sebagai latihan, aku akan mencoba membuat program yang mirip seperti program PING yang mengirim dan menerima packet ICMP ECHO.

Format packet ICMP ditentukan oleh RFC 792. Berikut ini adalah contoh packet ECHO REQUEST yang dikirim oleh program PING:

xx xx 08 00 4a 5c 02 00    01 00 61 62 63 64 65 66
67 68 69 6a 6b 6c 6d 6e    6f 70 71 72 73 74 75 76
77 61 62 63 64 65 66 67    68 68

Dan ini adalah contoh ECHO REPLY atas packet di atas:

xx xx 00 00 52 5c 02 00    01 00 61 62 63 64 65 66
67 68 69 6a 6b 6c 6d 6e    6f 70 71 72 73 74 75 76
77 61 62 63 64 65 66 67    68 69

Byte awal 08 menunjukkan kalau packet tersebut adalah ECHO REQUEST, dan byte 00 menunjukkan kalau packet tersebut adalah ECHO REPLY. Byte kedua (CODE) berisi nilai 00. Kemudian diteruskan dengan dua byte checksum. Dua byte berikutnya adalah IDENTIFIER, dilanjutkan dengan dua byte SEQUENCE NUMBER. Sepertinya program PING selalu menggunakan IDENTIFIER dengan nilai 0x0200. Nilai SEQUENCE NUMBER dipergunakan untuk mencocokkan antara request dan reply. Setelah itu PING mengirimkan 32 byte data yang berupa deretan huruf.

Sebelum memulai mengirim packet, aku harus memastikan bawah packet ICMP di-enkapsulasi lagi oleh packet IP, lalu oleh packet Ethernet. Untuk mempermudah pembuatan program, aku membuat sebuah struct sederhana yang merepresentasikan packet ECHO REQUEST seperti berikut ini:

struct ECHO_REQUEST {
  u_char mac_destination[6];
  u_char mac_source[6];
  u_short type;
  u_char ip_version_headerlength;
  u_char ip_services;
  u_short ip_totalLength;
  u_short ip_identification;
  u_char ip_flag;
  u_char ip_fragment_offset;
  u_char ip_ttl;
  u_char ip_protocol;
  u_short ip_checksum;
  u_char ip_source[4];
  u_char ip_destination[4];
  u_char  icmp_type;
  u_char  icmp_code;
  u_short icmp_checksum;
  u_short icmp_identifier;
  u_short icmp_sequence;
  u_char  icmp_data[32];
};

Jika aku ingin men-PING komputer dengan IP 192.168.1.1 dari komputer dengan IP 192.168.1.2, maka aku dapat mengisi setiap field di packet seperti:

void initializeEchoRequestPacket(ECHO_REQUEST *echoRequest) {
  u_char mac_destination[6] = {0x1, 0x1, 0x1, 0x1, 0x1, 0x1 };
  valuecpy(echoRequest->mac_destination, mac_destination, 6);
  u_char mac_source[6] = {0x2, 0x2, 0x2, 0x2, 0x2, 0x2 };
  valuecpy(echoRequest->mac_source, mac_source, 6);
  echoRequest->type = 0x0008;

  echoRequest->ip_version_headerlength = 0x45;
  echoRequest->ip_services = 0x00;
  echoRequest->ip_totalLength = 0x3C00;
  echoRequest->ip_identification = 0x0400;
  echoRequest->ip_flag = 00;
  echoRequest->ip_fragment_offset = 0x0000;
  echoRequest->ip_ttl = 0x80;
  echoRequest->ip_protocol = 0x01;
  echoRequest->ip_checksum = 0x0000;
  u_char ip_source[4] = {192,168,1,2};
  valuecpy(echoRequest->ip_source, ip_source, 4);
  u_char ip_dest[4] = {192,168,1,1};
  valuecpy(echoRequest->ip_destination, ip_dest, 4);

  echoRequest->icmp_type = 0x08;
  echoRequest->icmp_code = 0x00;
  echoRequest->icmp_checksum = 0x0000;
  echoRequest->icmp_identifier = 0x0002;
  echoRequest->icmp_sequence = 0x000d;
  for (int i=0; i<32; i++) {     echoRequest->icmp_data[i] = 'B';
  }
}

Agar packet ICMP ECHO REQUEST ini dapat dikirim dan diterima, selain mengisi IP address dengan benar, aku juga harus mengisi nilai MAC Source dan MAC Destination (yang merupakan bagian dari Ethernet Frame) dengan benar. Selain itu, aku juga harus melakukan kalkulasi untuk menghasilkan checksum dengan benar. Proses kalkulasi dan contoh kode program untuk kalkulasi checksum dapat dilihat di RFC 1071. Ini adalah contoh fungsi sederhana untuk menghitung nilai checksum untuk IP Header:

void calculateIPChecksum(ECHO_REQUEST *packet) {
  u_short *shortValue = (u_short*)
    (&packet->ip_version_headerlength);
  long totalValue = 0;

  for (int i=0; i<10; i++) {     
    totalValue += (*shortValue);     
    shortValue++;   
  }   
  while (totalValue>>16) {
    totalValue = (totalValue & 0xffff) + (totalValue>>16);
  }
  packet->ip_checksum = ~totalValue;
}

Fungsi di atas mengandaikan IP Header berukuran fixed 20 bytes atau 10 words. Kalkulasi checksum dilakukan dengan menjumlahkan seluruh word (integer 16-bit) yang membentuk IP Header. Jika hasil jumlah dalam bentuk angka 16-bit ini mengandung carry/overflow , maka carry dari MSB dijumlahkan ke LSB, dan ini dilakukan berulang sampai tidak ada carry lagi. Dan hasilnya, 1’s complement dari nilai tersebut adalah nilai cheksum yang dipakai.

Dan sebagai langkah terakhir untuk mengirim ECHO REQUEST, aku harus melakukan proses pengiriman packet. Cara yang paling gampang adalah dengan menggunakan fungsi pcap_sendpacket(). Sebagai contoh, aku mengirimkan 15 kali ECHO REQUEST dengan menggunaakan kode seperti berikut:

for (int j=0; j<15; j++) {   
  struct ECHO_REQUEST *echoRequest = 
    new struct ECHO_REQUEST();   
  initializeEchoRequestPacket(echoRequest);   
  echoRequest->ip_identification = (u_short) j;
  echoRequest->icmp_identifier = 0x0002;
  echoRequest->icmp_sequence = (u_short) j;
  calculateIPChecksum(echoRequest);
  calculateICMPChecksum(echoRequest);
  if (pcap_sendpacket(handle, (u_char*)((void*)echoRequest), 74)) {
    printf("ERROR: %s\n", pcap_geterr(handle));
    exit(EXIT_FAILURE);
  }
  delete echoRequest;
}

O ya, sebagai data pada ECHO REQUEST, aku mengirimkan 32 byte huruf ‘B’. Sebenarnya, aku punya kebebasan untuk memberikan nilai apa saja disini. Salah satu ide kreatif yang pernah aku temui adalah mengirimkan data melalui ICMP ECHO, seperti isi web page atau text chatting. Administrator cenderung memblokir komunikasi TCP/IP berdasarkan port. Jika ia berbaik hati membolehkan packet ICMP REQUEST keluar masuk dengan leluasa [test dengan mencoba apakah program PING dapat dipakai], maka seseorang bisa saja melakukan tunneling. Misalnya, request HTTP disisipkan sebagai data dalam packet ICMP ECHO REQUEST, kemudian diterima oleh komputer lain yang bebas dari blokiran. Program spesial di komputer lain tersebut membaca data spesial di ICMP ECHO REQUEST tersebut, lalu melakukan koneksi internet, dan mengirimkan hasilnya ke komputer pengguna, juga dalam bentuk ICMP ECHO REQUEST/REPLY. Sang administrator di ruangan khususnya, kini sedang serius membaca log firewall (atau mungkin membaca chat gadis-gadis QA sambil sesekali tertawa, no offense here, just a speculation), hanya akan melihat dua komputer saling nge-ping.

Network Packet Programming: Membaca Packet Dengan Callback

Setelah berhasil mendapatkan network device yang dapat di-sniffer, aku akan mencoba membaca packet milik network device tersebut. Aku harus mulai dengan fungsi pcap_open_live(). Setelah itu, untuk membaca packet, WinPcap menawarkan dua cara, dengan callback atao tanpa callback (cara biasa). Pada latihan kali ini, aku akan mencoba memakai callback, sehingga fungsi yang perlu aku panggil adalah pcap_dispatch() atau pcap_loop(). Aku akan memakai pcap_loop() yang akan terus menunggu hingga jumlah packet yang aku tentukan tercapai, walaupun hal ini dapat menyebabkan blocking jika jaringan sedang sibuk.

Untuk mempersingkat tulisan, aku tidak akan menyertakan kode program untuk menampilkan informasi device lagi. Aku hanya menambahkan sedikit kode yang memberi pilihan bagi user untuk menentukan device mana yang akan di-capture:

int pilihan=1, i=1;
printf("Masukkan nomor perangkat yang akan di-capture: ");
scanf_s("%d", &pilihan);
currentDevice = devices;
while (pilihan!=i) {
  currentDevice = currentDevice->next;
  i++;
}

Setelah itu, aku memanggil pcap_open_live() dengan kode seperti:

pcap_t *handle = pcap_open_live(currentDevice->name, 65535, 1, 1000, pesanError);
if (handle==NULL) {
  printf("ERROR: %s\n", pesanError);
  pcap_freealldevs(devices);
  exit(EXIT_FAILURE);
}

Pada pcap_open_live() di atas, aku men-capture maksimal 65.535 byte (seharusnya ini adalah maksimal ukuran kebanyakan packet); aku juga mengaktifkan modus promiscuous, yaitu tidak hanya menangkap packet yang ditujukan untuk komputer-ku tapi juga packet yang dalam perjalanan ke komputer lain tapi harus singgah sebentar ke komputer-ku (well, orang yang iseng pasti sangat suka ini!); dan aku menentukan read timeout sebesar 1000 ms.

Sampai pada bagian ini, aku tidak membutuhkan lagi informasi devices, sehingga aku bisa membebaskan alokasi memori untuk linked list yang berisi informasi tersebut. Setelah itu, aku bisa mendaftarkan fungsi callback:

pcap_freealldevs(devices);
pcap_loop(handle, 0, handlerPacketMasuk, NULL);

Sekarang, setiap kali ada packet yang masuk, fungsi handlerPacketMasuk() akan dikerjakan. Berikut ini adalah contoh fungsi tersebut:

void handlerPacketMasuk(u_char *user,
       const struct pcap_pkthdr *pkt_header,
       const u_char *pkt_data) {

  for (u_int i=1; i<=(pkt_header->len); i++) {
    printf("%02x ", pkt_data[i-1]);
    if (i%8==0) printf("\n");
  }

  printf("\n\n");

}

Fungsi di atas akan menampilkan isi packet dalam tampilan 8 byte per baris. Setidaknya hari ini aku sudah membuat sebuah program sniffer primitif.

Network Packet Programming: Percobaan Pertama

Sehubungan dengan topik yang sedang dipelajari, alangkah menyenangkannya jika aku dapat membuat program untuk menangkap dan mengirim packet secara langsung pada data link layer. Teman-temanku yang suka iseng pasti pernah menggunakan program serupa untuk menyadap atau memblokir koneksi internet seseorang. Tool tersebut harus dapat membuat packet palsu, dan ini hanya bisa dilakukan di data link layer. Sayangnya, Windows tidak menyediakan cara gampang untuk memodifikasi packet selain dengan membuat NDIS intermediate driver. Tapi kabar baiknya, ada sebuah library open source (plus driver siap jadi, tentunya) yang dapat digunakan untuk keperluan ini, yaitu WinPCap. Wireshark adalah salah satu tool yang memanfaatkan WinPCap untuk men-capture packet.

Setelah men-download WinPcap Developer Pack, aku akan mendapatkan beberapa header dan static library yang siap dipakai oleh program. Sekarang, aku akan menyiapkan project baru di Visual C++. Aku masih menggunakan Visual Studio 2008 dan sudah hampir setahun lebih aku tidak menyentuh gadis ini (kabarnya Visual Studio 2010 beta sudah dapat dicoba). Setelah sedikit meluangkan waktu untuk menyesuaikan diri, aku meng-import header-header yang ada. Aku juga tidak lupa menambahkan referensi bagi compiler, dengan men-klik kanan nama project dan memilih properties, C/C++, General. Lalu pada Additional Include Directories, aku menambahkan folder yang berisi header tersebut. Hal yang sama juga aku lakukan pada static library, dengan memilih Linker, Input, dan mengisi Additional Dependencies dengan lokasi static library milik WinPCap (wpcap.lib dan Packet.lib).

Aku mulai dengan membuat kode seperti:

#include "pcap.h"
#include <conio.h>

void exitHandler() {
  printf("Tekan sembarang tombol untuk keluar dari program...");
  _getch();
}

int main() {
  atexit(exitHandler);

  ...

  exit(EXIT_SUCCESS);
}

Langkah pertama adalah aku akan menampilkan perangkat network apa saja yang tersedia dan dapat dipergunakan oleh WinPcap, dengan memanggil fungsi pcap_findalldevs(). Bila ingin men-capture packet milik network card yang berada di jaringan remote (bukan di komputer lokal), aku harus menggunakan fungsi pcap_findalldevs_ex(). Syarat lainnya adalah komputer remote tersebut harus menjalankan daemon rpcapd.exe (terdapat di folder instalasi WinPcap).

pcap_if_t *devices;
char pesanError[PCAP_ERRBUF_SIZE];
if (pcap_findalldevs(&devices, pesanError)==-1) {
  printf("ERROR: %s\n", pesanError);
  exit(EXIT_FAILURE);
}

pcap_findalldevs() akan menghasilkan nilai -1 jika terjadi kesalahan saat mencari device apa saja yang dapat dipergunakan. Jika ia sukses, maka daftar device yang ada akan disimpan dalam struktur linked list pcap_if_t. Berikut ini adalah contoh kode program untuk menampilkan isi pcap_if_t:

#include "pcap.h"
#include <conio.h>

char *iptos(u_long in);

void exitHandler() {
  printf("Tekan sembarang tombol untuk keluar dari program...");
  _getch();
}

int main() {
  atexit(exitHandler);
  pcap_if_t *devices;
  char pesanError[PCAP_ERRBUF_SIZE];
  if (pcap_findalldevs(&devices, pesanError)==-1) {
    printf("ERROR: %s\n", pesanError);
    exit(EXIT_FAILURE);
  }

  pcap_if_t *currentDevice = devices;

  while (currentDevice != NULL) {

    printf("Device: %s\n", currentDevice->name);
    printf("Description: %s\n",
    currentDevice->description ? currentDevice->description : "(informasi tidak
      tersedia)");

    pcap_addr *address = currentDevice->addresses;
    while (address != NULL) {
      if (address->addr->sa_family == AF_INET) {
        sockaddr_in *sockaddr = (struct sockaddr_in *) address->addr;
        printf("Address: %s\n", iptos(sockaddr->sin_addr.s_addr));
      }
      address=address->next;
    }
    currentDevice = currentDevice->next;
    printf("\n\n");
  }

  pcap_freealldevs(devices);
  exit(EXIT_SUCCESS);
}

/* From tcptraceroute, convert a numeric IP address to a string */
#define IPTOSBUFFERS 12
char *iptos(u_long in)
{
  static char output[IPTOSBUFFERS][3*4+3+1];
  static short which;
  u_char *p;
  p = (u_char *)&in;
  which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);
  sprintf_s(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
  return output[which];
}

Program di atas juga menampilkan alamat IP untuk setiap device yang disimpan dalam struktur linked list pcap_addr jika ada. Untuk menerjemahkan alamat IP numerik dan menampilkan ke dalam bentuk String (berbahagialah programmer Java yang punya toString()), aku men-copy paste fungsi iptos() yang ada di dokumentasi WinPcap. Dan lagi-lagi tidak seperti di Java, aku harus membebaskan alokasi memory setelah aku tidak membutuhkannya, dalam hal ini sudah ada fungsi pembantu pcap_freealldevs().

ICMP: Sang Pemberitahu Masalah

Komunikasi pesan dari pengirim hingga penerima mungkin saja mengalami masalah ditengah saja. Untuk memberi tahu kepada pengirim bahwa telah terjadi suatu masalah, Internet Control Message Protocol (ICMP) siap bekerja. Berikut ini adalah contoh packet ICMP:

.. .. 03 03 6b 69 00 00  00 00 xx xx xx xx
xx xx xx xx xx xx xx xx  xx xx xx xx xx xx
...

Byte pertama menunjukkan jenis pesan ICMP. Nilai 03 menunjukkan ini adalah pesan “Destination Not Reachable”. Nilai lain yang mungkin adalah:

00 = Echo Reply
03 = Destination Not Reachable
04 = Source Quench
05 = Redirection Required
08 = Echo Request
11 = Time To Live Exceeded
12 = Parameter Problem
13 = Timestamp Request
14 = Timestamp Reply
17 = Address Mask Request
18 = Address Mask Reply

Banyak utility jaringan yang memanfaatkan ICMP, misalnya PING yang akan mengirim “Echo Request” dan memeriksa “Echo Reply”.

Byte kedua berisi kode untuk memperjelas pesan ICMP. Pada contoh di atas, byte kedua bernilai 03, yang menunjukkan informasi “Port unreachable”. Setelah itu ada dua byte checksum.

Byte ke-lima sampai byte ke-delapan seharusnya berisi parameter. Tetapi tidak semua pesan ICMP mengandung parameter, dan kebetulan “Destination Not Reachable” tidak membutuhkan parameter, sehingga nilainya 00 semua.

Byte berikutnya merupakan isi dari pesan, dan layout-nya berbeda-beda tergantung pada jenis pesan ICMP. Pada contoh di atas, byte berikutnya mengandung packet IP asli yang mengalami “Destination Not Reachable”. Packet asli tersebut tidak ditampilkan, hanya berupa tanda “xx” saja.