Aplikasi Go Mengandung Debug Info Secara Default
Pada saat saya sedang meningkatkan pengetahuan forensik digital dengan memahami metadata apa saja yang tersimpan dalam file multimedia (seperti video & audio), saya menulis sebuah program Go jch-metadata sebagai latihan praktikum-nya. Saya memilih Go karena saya bisa menjalankan aplikasi ini secara langsung tanpa perlu instalasi tambahan. Saya cukup menggunakan perintah go build
untuk menghasilkan file binary jch-metadata
yang bisa langsung dijalankan di Linux atau GOOS=windows GOARCH=amd64 go build
untuk menghasilkan file binary jch-metadata.exe
yang bisa langsung dijalankan di Windows. Namun, saya cukup terkejut saat menemukan bahwa aplikasi yang saya buat untuk membaca metadata tersebut ternyata juga mengandung metadata yang bisa dipakai untuk mengidentifikasi saya!
Pembuktian
Untuk menunjukkannya, saya akan membuat sebuah aplikasi Go sederhana seperti berikut ini:
Kode program di atas akan mencetak stack dan berhenti dengan pesan kesalahan. Saya dapat men-build kode program di atas dengan perintah seperti berikut ini:
Hasilnya adalah sebuah binary dengan nama test
. Ini adalah aplikasi publik yang dapat saya distribusikan tanpa harus mempublikasikan kode program saya. Bila saya menjalankan aplikasi ini di komputer lain, saya akan menemukan hasil seperti berikut ini:
Terlihat bahwa aplikasi tetap mencetak lokasi Go SDK di komputer yang saya pakai untuk melakukan building. Selain itu, aplikasi ini juga tetap menyimpan informasi nama file di kode program Go saya: /tmp/test/main.go
. Fitur ini sangat bagus untuk mempermudah troubleshooting bila terdapat kesalahan yang dilaporkan oleh pengguna karena terdapat informasi nama file dan nomor baris secara detail di pesan kesalahan.
Walaupun demikian, informasi ini juga dapat dipakai untuk menebak sistem operasi dan nama user yang saya pakai. Sebagai contoh, nama file seperti /home/developer/Documents/app/main.go
menunjukkan bahwa ini file ini dibuat di Linux dengan nama user developer
. Ini mungkin bukan informasi sensitif bila seandainya proses building dilakukan secara otomatis lewat container yang tidak permanen, namun informasi ini menjadi lebih berharga bila proses build dilakukan langsung dari laptop kerja-nya programmer. Untuk beberapa kasus, seperti pengembangan malware, informasi ini sensitif karena bisa dipakai untuk klasifikasi oleh tim threat intel (misalnya mendeteksi apakah beberapa malware berbeda dibuat oleh programmer yang sama).
Apa saja metadata yang tersimpan di sebuah aplikasi yang dibuat dengan bahasa Go?
DWARF
Format yang dipakai oleh aplikasi Linux mengikuti spesifikasi Executable and Linkable Format (ELF). Pada dasarnya, sebuah file ELF terdiri atas ELF header yang di-ikuti oleh program header dan section header. Kode program yang akan di-eksekusi oleh CPU (dalam bahasa mesin) akan ditulis di section .text
. Section lainnya selain .text
biasanya hanya berisi informasi pendukung.
Sebagai contoh DWARF sebagai standar format debugging yang paling umum dipakai akan menambahkan informasi section seperti .debug_info
, .debug_pubnames
, dan sebagainya (semua yang diawali dengan .debug_
). Sebagai informasi, pada proses building dengan go build
, Go secara default akan menambahkan section DWARF tersebut. Namun bila menggunakan hex editor, saya tidak akan menemukan daftar nama file di section DWARF secara langsung karena informasi di DWARF dikompres secara default sejak Go 1.11 (dengan tujuan untuk mengurangi ukuran file).
Untuk men-dekompres informasi DWARF, saya dapat memberikan perintah:
Setelah perintah di atas diberikan, saya akan menemukan file decompressed-test
dengan ukuran yang lebih besar dibanding test
(2,9M versus 1,8M). Bila saya membuka file ini melalui hex editor, saya dapat melihat informasi nama file source code dengan mudah.
Bagian paling utama dari informasi debugging DWARF berada di section .debug_info
. Namun, informasi nomor baris dan nama file untuk sebuah kode program berada di section lain yang bernama .debug_line
. Saya bisa menampilkannya dengan menggunakan perintah objdump
seperti berikut ini:
Untuk menghilangkan informasi DWARF, pada saat building, saya dapat menambahkan argumen -ldflags="-w"
seperti pada contoh berikut ini:
Sekarang, bila saya memberikan perintah objdump
, saya tidak akan menemukan lagi daftar lokasi source code seperti yang terlihat pada hasil eksekusi berikut ini:
Selain itu, ukuran file juga menjadi lebih kecil dari semula (1,3M vs 1,8M).
Symbol Table
Section berikutnya yang dapat dihilangkan adalah symbol table yang berada di .symtab
. Bagian ini berisi nama untuk setiap data yang ada. Untuk melihatnya, saya dapat menggunakan perintah objdump
seperti berikut ini:
Walaupun tidak mengandung nama file, saya dapat menghilangkannya dengan menggunakan argumen -ldflags="-s"
seperti pada contoh berikut ini:
Hasil akhirnya adalah binary yang lebih kecil (1,2M vs 1,3M). Selain itu, bila saya memberikan perintah objdump -t test
, tidak ada lagi informasi symbol table yang ditampilkan.
Go Runtime Symbol Information
Go Runtime Symbol Information adalah fitur khusus Go yang menambahkan section .gopclntab
dan .gosymtab
pada file ELF yang dihasilkan. Bila DWARF dan symbol table adalah fitur umum pada ELF, Go Runtime Symbol Information adalah fitur spesifik pada Go. Fitur ini dipakai oleh garbage collector milik Go, menghasilkan stack trace yang akurat, dan sebagainya. Dengan demikian, bila sebuah aplikasi mengandung section .gopclntab
dan .gosymtab
, maka aplikasi tersebut kemungkinan besar dibuat dari bahasa pemograman Go.
Untuk menampilkan isi .gopclntab
, saya dapat menggunakan perintah seperti berikut ini:
Terlihat bahwa .gopclntab
juga menampung informasi nama file source code. Ini adalah salah satu penyebab mengapa bila saya menjalankan aplikasi test
, saya masih tetap menjumpai output lokasi file yang akurat walaupun saya sudah menghapus DWARF dan symbol table (dengan -ldflags="-w -s"
).
Kabar buruknya adalah tidak ada flag untuk menghilang section .gopclntab
. Dan seandainya saja saya berhasil menghapus .gopclntab
, berdasarkan informasi dari https://github.com/golang/go/issues/36555, beberapa fitur dari Go yang menggunakan runtime.Callers
akan berhenti bekerja. Sebagai gantinya, Go menawarkan -trimpath
untuk menghapus informasi lokasi folder secara penuh. Sebagai contoh, saya dapat menggunakan seperti pada perintah berikut ini:
Sekarang, bila saya menjalankan aplikasi, saya akan mendapatkan hasil seperti berikut ini:
Nama file pada hasil sebelumnya /tmp/test/main.go
kini menjadi lebih singkat seperti /test/main.go
. Ini akan membantu mengurangi jumlah informasi yang di-ekspos.
Bila ini tidak cukup, saya juga dapat menerapkan teknik obfuskasi. Sebagai contoh, saya menambahkan fitur ini di jch-metadata
yang bisa dijalankan dengan perintah seperti berikut ini:
Kode program jch-metadata
akan menggunakan package bawaan debug/gosym
untuk membaca Go Runtime Symbol Information. Kode program-nya dapat dilihat di https://github.com/JockiHendry/jch-metadata/blob/bbf5e1ac28ac26aae72e4c10807aebb02fbaa0b7/internal/parser/elf/elf.go. Sekarang, bila saya menjalankan test
, saya akan memperoleh hasil seperti berikut ini:
Build ID
Secara default, aplikasi yang dihasilkan oleh Go akan memiliki sebuah section dengan nama .note.go.buildid
. Saya dapat melihat isinya dengan memberikan perintah seperti berikut ini:
Selain itu, saya juga dapat melihat nilai yang sama dengan menggunakan perintah go tool
seperti berikut ini:
Setiap kali saya men-compile kode program yang sama, saya akan memperoleh build ID yang sama. Namun bukan hanya itu saja. Bila seandainya salah satu dari build tool yang saya pakai berubah, misalnya versi Go yang berbeda, biarpun kode program-nya sama, aplikasi yang dihasilkan akan memiliki build ID yang berbeda.
Sama seperti .gopclntab
, tidak ada flag dari Go untuk menghapus section .note.go.buildid
. Bila saya menggunakan jch-metadata -f test -a clear
seperti di bagian sebelumnya, nilai Build ID akan itu di-acak.
Build Info
Aplikasi yang dibuat dengan Go juga memiliki section dengan nama .go.buildinfo
. Section ini berisi informasi seperti versi Go dan flag yang dipakai saat melakukan building. Selain itu, bila kode program yang di-build dikelola oleh VSC seperti Git, nilai hash Git yang aktif saat melakukan building juga akan turut disimpan di section ini. Sebagai contoh, saya dapat melihat isi section ini dengan menggunakan perintah seperti berikut ini:
Data yang ada di section .go.buildinfo
ini dapat dibaca di aplikasi melalui package runtime/debug
seperti pada contoh berikut ini:
Sama seperti section .go
lainnya, saya tidak menemukan fitur bawaan Go untuk tidak menyertakan section ini di aplikasi yang dihasilkan. Namun, bila tidak ingin menyertakan informasi VCS di Build Info, saya dapat menambahkan flag -buildvcs=false
seperti pada contoh berikut ini: