Mengenal Bahasa Pemrograman Forth

Di posting ini saya ingin memperkenalkan bahasa pemrograman Forth. Bahasa Forth sudah ada selama 50 tahun dan cukup menarik walaupun saat ini sudah cukup jarang dipakai. Forth memakai notasi postfix, berbasis stack, dan merupakan contoh dari concatenative programming language.

Forth merupakan bahasa yang sangat sederhana dan dapat diimplementasikan di sistem dengan spesifikasi sangat rendah, bahkan juga di microcontroller. Biasanya Forth bisa diprogram secara interaktif (analoginya seperti berada di shell Python/Ruby), tapi bisa juga dikompilasi.

Sifat interaktif Forth ini seperti berbagai bahasa tingkat tinggi. Di sisi lain Forth bisa mengakses mesin secara low level dan tidak memiliki struktur data yang rumit (misalnya hash atau list),hanya dapat mengatur blok memori secara low level (seperti C). Jadi Forth berada antara bahasa tingkat tinggi dan tingkat rendah.

Di mana Forth dipakai?

Dari puluhan ribu bahasa pemrograman di dunia ini, tidak banyak bahasa pemrograman yang memiliki standard ISO, Forth merupakan salah satu bahasa yang memiliki standard ISO. Beberapa contoh bahasa dengan standar ISO adalah: JavaScript, Ruby, SQL, Pascal, C, dan C++, sementara Python, Go, Dart dsb tidak/belum punya standar ISO. Adanya standar sebuah bahasa biasanya membuat bahasa tersebut dapat dipakai di berbagai proyek yang memiliki spesifikasi sangat ketat (misalnya proyek NASA).

Di masa kejayaannya ada banyak sekali program yang ditulis dalam Forth untuk berbagai aplikasi ruang angkasa, misalnya pesawat ruang angkasa Philae yang mendarat di komet. Banyak proyek Forth yang bisa dilihat di sini.  Sebelum Apple beralih ke Intel, komputer Apple memakai Open firmware (seperti BIOS di PC) yang menggunakan Forth.

Saat ini saya belum menemukan daftar terbaru di mana Forth masih dipakai secara komersial atau dalam skala besar. Namun demikian, saya melihat masih banyak proyek open source yang aktif untuk microcontroller dan proyek IOT.

Interpreter/Compiler Forth

Saat ini ada banyak implementasi Forth untuk berbagai sistem baik komersial maupun open source. Untuk implementasi open source, daftar yang cukup lengkap bisa dilihat di Github. Awalnya Forth dirancang untuk berjalan di komputer tanpa operating system, jadi mungkin ada beberapa konsep yang agak membingungkan atau agak aneh jika dilihat dari kacamata sistem operasi saat ini.

Salah satu demo Gforth di Android

Pada artikel ini saya akan menggunakan Gforth yang dapat berjalan di Windows/Linux/OS X dan juga di Android. Saya juga akan membahas sedikit Forth untuk embedded system.

Jika Anda ingin mencoba versi Android, setelah instalasi, pergi ke settings untuk mengaktifkan permision “Storage”. Tanpa permission ini GForth tidak bisa mengekstrak contoh program ke /sdcard/gforth.

Aktifkan permission storage

Hello World

Bentuk hello world sederhana dengan gforth seperti ini.

." Hello World" cr

Perhatikan bawah tidak ada spasi antara . (titik) dan ” (petik), lalu ada spasi sebelum Hello (ini sangat penting ketika mencoba contoh tersebut). Contoh Hello World kurang menunjukkan keunikan Forth, jadi untuk contoh berikut, saya menggunakan mode interaktif Gforth untuk operasi matematika.

Untuk menambahkan dua buah angka dan mencetak hasilnya:

3 4 + .

Dalam contoh di atas, yang terjadi adalah: 3 akan dipush ke stack, 4 akan dipush ke stack, + (plus) akan mengambil 2 angka di stack dan menjumlahkan keduanya dan menaruh hasilnya di stack, . (titik) akan mengambil angka dari stack dan mencetak di layar.

Agar outputnya terlihat terpisah dari baris lain, kita bisa menambahkan cr, seperti ini (akhiri ini dengan menekan ENTER):

cr 3 4 + .

Forth memakai istilah “word” untuk semua simbol dan bilangan yang diparse oleh interpreter/compiler. Di posting ini saya tidak akan menerjemahkan “word” sebagai “kata”, supaya tidak bingung ketika membaca dokumentasi Forth.

Secara umum interpreter Forth bekerja seperti ini: interpreter akan memecah input menjadi word dan menjalankan aksi dari tiap word. Semua simbol dan bilangan yang dipisahkan spasi/enter dianggap sebagai satu word (jadi “123” adalah 1 word, “.” adalah satu word).

Aksi untuk word yang bisa dikonversi mernjadi bilangan adalah memasukkan (push) bilangan ke stack, aksi untuk operasi matematika adalah mengambil isi 2 elemen stack teratas, melakukan operasinya dan menaruh hasilnya di stack. Implementasi Forth ada yang case sensitive dan ada yang tidak. GForth merupakan contoh yang tidak, jadi besar kecil dianggap sama. Dalam Standard Forth, semua word standar harus dalam UPPERCASE.

Operasi matematika ini: (2 + 3) * 5 bisa dituliskan di forth seperti ini:

2 3 + 5 *

Sebagian orang sangat menyukai notasi postfix ini dan dulu ada banyak kalkulator yang memakai Reverse Polish Notation (RPN), bahkan semua kalkulator dari HP versi awal memakai notasi RPN.  Meskipun aneh, tapi notasi seperti ini hemat menekan tombol tidak butuh kurung untuk presedensi operator (seperti pada contoh di atas).

Bagaimana kita tahu apa saja yang ada di stack? kita bisa menginspeksi stack dengan word “.S”, ini akan memprint isi stack, contohnya

1 2 3 .S \ ada 3 isi stack; 1 2 3

Perhatikan backslash  adalah cara untuk membuat komentar (perlu diikuti spasi karena backslash adalah sebuah word) dan saya pakai untuk menjelaskan jadi jika ingin mengikuti contohnya di interpreter abaikan \ dan seterusnya. Untuk contoh di atas, cukup ketik:

1 2 3 .S

Jika kita jumlahkan dua isi stack teratas (2 dan 3) dengan +, lalu kita print lagi isi stacknya:

1 2 3 .S \ isi stack: 1 2 3
+ .S \ isi stack: 1 5

Top of stack menjadi 5.

Di interpreter Forth yang lain ada yang langsung mencetak isi stack di prompt (misalnya PunyForth), jadi kita tidak perlu melakukan apapun.

Ada banyak word yang mengoperasikan stack. Dalam dokumentasi  biasanya operasi dituliskan dalam bentuk komentar kurung buka ‘(‘ isi komentar lalu kurung tutup ‘)’.Komentar akan berisi stack sebelum dan sesudah word tersebut. Contohnya DUP untuk menduplikasi top of stack. Jika tadinya isi stack adalah A maka akan menjadi 2 yaitu A A. Di dokumentasi akan ditulis seperti ini:

DUP ( a -- a a)

Contoh lain, dokumentasi SWAP adalah seperti ini:

SWAP ( a b -- b a )

Dan OVER seperti ini:

OVER ( a b -- a b a )

Ada beberapa word di Forth yang sifatnya khusus, karena tidak mengoperasikan stack atau langsung dieksekusi, tapi akan melihat pada word berikutnya sampai batas tertentu. Kita mulai dari word pertama yang sifatnya khusus yaitu : (titik dua) untuk mendefinisikan word baru sampai ;

Contohnya kita ingin membuat word baru: tambahtujuh yang akan menambahkan 7 pada sebuah bilangan:

: tambahtujuh 7 + ;

Sekarang kita bisa melakukan ini

5 tambahtujuh .

dan hasilnya adalah 12. Untuk Anda yang penasaran dengan implementasi low level: Forth memiliki beberapa stack. Stack untuk data berbeda dengan stack yang dipakai untuk pemanggilan word (subrutin).

Di Forth, programmer mendefinisikan banyak word baru. Ini seperti subrutine/prosedur di sebuah bahasa. Setiap word hanya melakukan sedikit aksi. Dengan membuat word baru berdasarkan word sebelumnya, maka kita bisa membuat program yang terstruktur. Bisa dilihat bahwa di sini pendekatannya adalah bottom up.

Word : (titik dua) merupakan “defining word” dan kadang disebut juga “parsing word”, karena tidak seperti word biasa, word ini akan melihat pada input berikutnya, tidak hanya melihat stack dan mengeksekusi berdasarkan stack. Contoh defining word yang lain adalah adalah VARIABLE.

VARIABLE tanggal \ tanggal adalah variabel
12 tanggal ! \ set nilai tanggal menjadi 12
tanggal @ . \ akses variabel tanggal dan masukkan isinya ke stack, lalu print isi stack
tanggal ? \ langsung print isi variabel tanggal
7 tanggal +! \ tambahkan 7 ke nilai tanggal
tanggal ? \ sekarang tanggal menjadi 19

Konstanta juga bisa didefinisikan dengan CONSTANT.

144 CONSTANT LIMIT \ LIMIT nilainya 144

Loop yang diketahui batasnya bisa dibuat dengan

   FORMULA:
           limit index DO ... LOOP

Contoh untuk memprint kata “Bebek” 10 kali

: 10BEBEK 10 0 DO ." Bebek " CR LOOP ;
10BEBEK

Ada word bernama I (huruf i kapital) yang dipakai untuk menyalin isi stack untuk loop ke stack saat ini.

: 10BEBEK 10 0 DO I . ." Bebek " CR LOOP ; ok
10BEBEK

Jika kita mendefinisikan ulang word yang sudah ada, maka definisi terkhir yang dipakai, tapi definisi awal tidak dibuang.

Tentunya tidak semua hal bisa dikerjakan hanya dengan operasi stack. Forth juga memiliki control flow seperti IF dan LOOP. Bentuk IF di FORTH sintaksnya agak aneh dan tidak seperti di bahasa lain. Bentuknya adalah KONDISI IF AKSI THEN. Perlu diperhatikan bahwa menurut standar Forth semua control flow harus dimasukkan ke dalam definisi word

200 CONSTANT LIMIT
: lewatbatas LIMIT > IF cr ." Lewat batas" cr THEN ;

Untuk conditional loop kita bisa memakai BEGIN UNTIL jika pemeriksaan dilakukan di akhir aksi (seperti do while{}), atau BEGIN WHILE REPEAT jika pemeriksaan dilakukan di awal (seperti while () {} ). Jika kita ingin menghentikan loop di tengah (break), kita bisa menggunakan  LEAVE.

Berikut ini contoh sederhana dalam satu baris

: bebek 0 counter ! BEGIN counter ? ." Bebek" cr  1 counter +! counter @ 10 > UNTIL ; 

Tapi itu sulit dibaca, jadi biasanya ini akan dituliskan dengan banyak word supaya lebih jelas. Word pertama “reset” untuk mengeset counter menjadi 0, word “full?” akan mengecek apakah counter sudah bernilai lebih dari 10, word “next” akan menambah counter dengan 1.

VARIABLE counter
: reset 0 counter ! ;
: full? counter @ 10 > ;
: next 1 counter +! ;
: bebek reset BEGIN counter ? ." Bebek" cr next full? UNTIL ;

Literal string  didefinisikan dengan S”, seperti ini S" hello world" dan dicetak dengan type. Sebagai shortcut, kita bisa memakai ." string" untuk mencetak string langsung ke layar.

Kita bisa melihat definisi suatu word dengan “SEE”.

SEE lewatbatas
: lewatbatas
200 >
IF cr .\" Lewat batas" cr
THEN ; ok

Yang menarik, andaikan kodenya diimplementasikan dalam bahasa mesin, maka bahasa assemblynya akan ditampilkan.

Struktur Data

Saya tidak akan membahas panjang mengenai struktur data di Forth, karena Forth hanya memiliki primitif semacam malloc di C, dan sisanya adalah implementasi oleh programmer.

Forth bekerja dalam satuan CELL. Ukuran satu sel tergantung implementasi, tapi di Standar Forth ditentukan minimal 16 bit. Masalahnya tidak semua tipe data muat dalam 1 sel (misalnya tipe data double butuh 8 byte sedangkan integer 32 bit hanya butuh 4 byte), jadi kadang di Forth kita perlu mengoperasikan sepasang nilai di stack. Segala operasi ini dilakukan dengan Word yang berawalan dengan angka 2, misalnya 2DUP.

Word yang dipakai untuk mengalokasikan memori adalah ALLOT. Setelah memori dialokasikan, maka isinya bisa diakses dengan operasi aritmatika terhadap pointer (seperti di C). Contohnya untuk mengalokasikan Array 4 sel, lalu mengisi sel kedua dengan nilai 5:

VARIABLE a 4 CELLS ALLOT
5 a 1 CELLS + !

Ini mirip di C jika kita menggunakan aritmatika pointer.

a = (int*)malloc(4);
*(a+1) = 5;

Forth untuk embedded system

Karena Forth bisa berjalan langsung di microcontroller — termasuk juga mode interaktifnya — maka Forth memudahkan prototyping. Jika menggunakan C maka kita harus menulis program di desktop, mengcompile programnya (bisa beberapa detik hingga puluhan detik), mentransfer ke microcontroller dan melihat hasilnya. Dengan interpreter interaktif di microcontroller, maka kita bisa langsung menggunakan serial port dan langsung memprogram.

Sebagai catatan, kenyamanan ini dirasakan banyak orang sehingga sekarang ini sudah ada beberapa proyek untuk menjalankan bahasa yang lebih “modern” seperti Lua, Python dan bahkan JavaScript di berbagai microcontroller yang memiliki RAM besar. Tapi dari pengalaman saya, berbagai implementasi yang saya coba cukup lambat karena tidak dirancang dari awal untuk berjalan di embedded system. Bahasa-bahasa tersebut juga tidak dapat dijalankan di sistem yang RAM-nya sangat kecil (misalnya Forth untuk STM8S bisa berjalan dengan RAM satu kilobyte).

Wemos D1 Mini

Jika ingin mencoba Forth di embedded system, mungkin saat ini yang paling mudah dan murah adalah ESP8266, versi Wemos D1 hanya 2.75 USD dan hanya perlu kabel micro USB untuk koneksi ke komputer. Proses memasukkan interpreter Forth dan melakukan koneksi serial tidak perlu hardware lain.

Board STM8S

Board yang paling murah dan bisa menjalankan Forth setahu saya adalah STM8S yang harganya kurang dari 1 USD (termurah saat ini 0.86 USD, harga satuan, sudah termasuk ongkir dari China, bisa lebih murah jika beli banyak). Meskipun paling murah, tapi untuk memasukkan interpreter Forth kali pertama perlu memakai ST-LINK (Sekitar 2 USD) dan untuk memprogram secara interaktif butuh USB to Serial (1 USD). Setelah program berjalan, kedua hardware tersebut tidak dibutuhkan lagi. STM8 eForth bahkan mendukung background task di microcontroller ini.

Penutup

Artikel ini hanya memperkenalkan “kulitnya” Forth saja, banyak hal yang tidak saya jelaskan. Jika Anda ingin membaca lebih lanjut, ada banyak resource di Internet, misalnya bisa mulai dari ini:

Starting FORTH

 
Bahkan standard Forth juga bisa dibaca. Versi Draftnya (yang katanya 99.9% sama dengan versi finalnya) bisa dibaca di sini. Isi standarnya cukup ringkas, hanya penjelasan word yang termasuk dalam standar yang membuat dokumennya menjadi panjang.

Sebagai tambahan juga, saya menemukan ada juga seseorang di Indonesia yang memakai Forth untuk produknya: https://telinks.wordpress.com/.

Code Obfuscation

Code Obfuscation adalah salah satu bentuk proteksi agar kode sulit dibongkar orang lain. Inti dari obfuscation adalah menyamarkan/membuat kode sulit dibaca. Obfuscation bisa dilakukan manual atau dengan tool yang disebut “obfuscator”. Sementara itu dari sisi reverse engineering, proses mengembalikan dari bentuk samar ini disebut “deobfuscation”.

Arti kata “samar” di KBBI

Meskipun kata “samar” sepertinya cukup berpadanan dengan “obfuscated” saya akan tetap menggunakan istilah inggrisnya di posting ini.

Di posting ini saya hanya ingin memberikan beberapa ilustrasi nyata seperti apa obfuscation ini. Untuk para programmer, ini bisa membantu memproteksi  program, dan untuk para reverse engineer bisa berusaha memahami bagaimana obfuscation dilakukan.

Contoh obfuscated code

Obfuscation

Jika kita punya dua fungsi sederhana seperti ini:

int search(int element, int *data, int count)
{
    for (int i =0; i < count; i++) {
          if (data[i] == count)
                return i;
    }
    return -1;
}

int replace_first(int element, int replacement, int *data, int count)
{
    int pos = search(element, data, count);
    if (pos !=-1) {
        data[pos] = replacement;
        return 1;
    }
    return 0;
}


Bentuk obfuscation pertama adalah dengan mengganti nama menjadi nama lain. Ini bisa jadi nama yang sangat singkat misalnya “a”. Pada kode yang dikompilasi menjadi bahasa mesin, nama ini bahkan tidak ada lagi karena tidak diperlukan. Pada bahasa lain yang tidak dicompile jadi bahasa mesin (seperti Java dan Python), nama ini akan tetap ada.

Tanpa membaca kodenya, sudah sulit menebak apa fungsinya ini

int a(int a1, int *a2, int a3);
int b(int a1, int a2, int *a2, int a3)

Bahasa tertentu (seperti Java/C++) mendukung overloading. Beberapa fungsi bisa memiliki nama sama asalkan parameternya berbeda:

int a(int a1, int *a2, int a3);
int a(int a1, int a2, int *a2, int a3)

Cara lain adalah dengan mengubah namanya menjadi menyesatkan, misalnya kedua nama seperti di atas diubah menjadi seperti ini (perhatikan bahwa sengaja count-nya dipindah ke depan untuk menyesatkan pembacanya).

int clear(int count, int *data, int position);
int remove_range(int start, int end, int *data, int element);

Selain penggunaan nama yang menyesatkan, cara lain adalah dengan mengubah control flow program. Contoh sederhananya seperti ini, kita memanggil beberapa fungsi berurutan:

verify_license();
initialize_printer();
initialize_camera();
connect_to_database();

Jika sebuah bahasa mendukung goto maka bentuk obfuscationnya bisa seperti ini:

verify_license();
goto label2;

label1:
initialize_camera();
connect_to_database();
goto label4;

label2:
initialize_printer();
goto label1;

label4:

Jika suatu bahasa tidak mendukung goto urutan operasi bisa disamarkan dengan loop dan switch:

int order[] = {3,1,2,0};
for (int i =0; i < 4; i++) {
   switch (order[i]) {
        case 0: connect_to_database(); break;
        case 1: initialize_printer(); break;
        case 2: initialize_camera(); break;
        case 3: verify_license(); break;
   }
}

Obfuscation kecil seperti contoh di atas masih mudah dimengerti untuk kode yang pendek. Untuk kode yang besar, jumlah “case”-nya bisa puluhan, dan masing-masing nama fungsinya tidak jelas.

Beberapa obfuscation bisa mudah dilihat jika data atau string terlihat jelas, misalnya jika ada kode seperti ini, meskipun kita tidak tahu apa itu kelas x, tapi terlihat bahwa fungsi saat ini berhubungan dengan enkripsi AES:

            throw new x("AES decrypt error");

Jadi bentuk obfuscation berikutnya adalah: string encryption supaya tidak mudah mencari string di dalam program dan mempersulit pemahaman. Tentunya string ini harus bisa didekrip ketika program berjalan, hanya mempersulit pemahaman program.

            throw new x(decrypt(ConstString.ERR1));

Teknik-teknik lain juga bisa ada banyak, saya tidak akan memberikan contoh kode satu persatu. Beberapa yang bisa dilakukan misalnya

  1. Obfuscation di level bahasa mesin/bytecode
  2. Menggunakan exception untuk control flow obfuscation
  3. Menyisipkan kode sampah, misalnya mengurutkan elemen, lalu mencari elemen tengah, menjumlahkan semua elemen, lalu hasilnya tidak dipakai
  4. Menggunakan thread untuk memecah algoritma menjadi beberapa bagian sehingga lebih sulit di mengerti
  5. Mengubah logika program, misalnya menambahkan angka 2312312 di awal, lalu di akhir dikurangi lagi 2312312
  6. Menggunakan enkripsi dan/atau kompresi untuk sebagian kode program yang diload secara dinamis
  7. Memakai custom virtual machine

Perlu diperhatikan bahwa obfuscation tertentu bisa membuat program jadi lebih lambat. Sekedar mengganti nama method tidak akan membuat lebih lambat (bahkan biasanya malah membuat sedikit lebih cepat), tapi mengganti control flow biasanya membuat kode menjadi lebih lambat. Obfuscation juga membuat debugging menjadi lebih sulit (karena memang itu tujuannya). Jadi sebaiknya obfuscation hanya dilakukan di akhir development.

Saya tidak bisa menyarankan tool obfuscator tertentu karena memang jarang melakukan obfuscation pada kode saya, silakan search “obfuscator” dan nama bahasa yang Anda pakai di search engine. Tool obfuscator yang banyak saya temui adalah  Proguard (gratis) yang dipakai untuk kode Java/Android.

Di dunia Javascript ada istilah minifier, yaitu tool untuk membuat kode Javascript mejadi lebih kecil. Ini dilakukan dengan mengganti nama variabel, menghapus spasi, komentar dsb. Secara umum ini juga berfungsi sebagai obfuscator sederhana.

Deobfuscation

Secara umum tidak ada cara generik yang membuat obfuscated code bisa dibaca dengan mudah. Tapi ada beberapa tool yang bisa membantu proses deobfuscation spesifik untuk bahasa/teknologi tertentu.

Contohnya jika bertemu dengan kode JavaScript yang sudah minified, maka kita bisa memakai JavaScript beautifier. Ini tidak bisa mengembalikan nama variabel, hanya membuat teks yang sulit dibaca menjadi lebih mudah dibaca (tool beautifier ini sekarang sudah built in di Developer Tools-nya Google Chrome).

Contoh lain: ada yang membuat tool untuk mendekrip String untuk APK yang ditulis dalam Java. Tapi tool ini tidak selalu jalan untuk semua protektor. Setiap kali ada yang membuat tool untuk otomasi sesuatu, pembuat obfuscator menambahkan satu hal kecil sehingga toolnya harus diupdate (atau bahkan ditulis ulang).

Cara yang pasti berhasil adalah kombinasi manual dengan debugger dan sedikit programming.  Hal utama adalah memahami obfuscation apa yang dilakukan: apakah stringnya dienkripsi, apakah nama methodnya diubah, apakah control flow-nya berubah, dsb.

Jika sekedar nama methodnya diubah, maka kita harus membaca kodenya dan melakukan renaming untuk mendapatkan nama yang benar. Ini bisa dilakukan berdasarkan beberapa hal, misalnya:

  • string yang muncul (misalnya “AES Error” mengindikasikan AES)
  • konstanta yang dipakai (misalnya 0x9E3779B9 mengindikasikan penggunaan enkripsi XTEA)
  • fungsi yang memanggil. Misalnya jika suatu fungsi dipanggil dari fungsi AES, maka kemungkinan itu hanyalah subrutin AES
  • fungsi yang dipanggil, misalnya jika aplikasi C memanggil “system” maka kemungkinan ini fungsi menjalankan command line lain
  •  algoritma yang dipakai. Beberapa algoritma sederhana (search, sort, traversal) mudah diidentifikasi

Untuk mendapatkan gambaran sebuah program seperti menyusun sebuah puzzle. Kita bisa mulai dari bagian-bagian yang jelas. Untuk puzzle bagian yang jelas adalah pinggiran puzzle, dan bagian-bagian yang unik. Untuk program kita bisa mulai dari titik awal program (main di sistem POSIX, Activity di Android, dsb), dan titik di mana program memanggil fungsi eksternal.

Jika string dienkripsi, kita bisa membuat breakpoint di method dekrip-nya agar bisa mendapatkan hasil string-nya. Jika control flow-nya obfuscated, kita bisa membuat beberapa breakpoint untuk berhenti di tiap titik, jadi kita bisa mengetahui urutan yang sebenarnya. Selain menggunakan breakpoint dan debugger, kita juga bisa menggunakan Frida atau tool sejenis.

Penutup

Walaupun obfuscator bisa membantu melindungi program, tapi faktor keamanan lain harus tetap diperhatikan. Attacker yang gigih akan bisa membuka segala jenis obfuscation, hanya akan memperlama saja. Di kasus tertentu kombinasi obfuscator dan pentester yang kurang berpengalaman justru bisa membuat aplikasi kurang aman karena testing aplikasi kurang optimal.

Contohnya begini: aplikasi memakai enkripsi, lalu kodenya diobfuscate.  Aplikasi ini ditest oleh pentester yang tidak bisa melakukan reverse engineering terhadap kode tersebut, dan pentester menganggap aplikasi tersebut aman karena dia tidak dapat melakukan tampering terhadap nilai yang dikirimkan. Ketika aplikasi dirilis dan dibongkar oleh seorang reverse engineer yang berpengalaman, dia dapat mengubah nilai yang dikirimkan dan ternyata tidak dicek di sisi server (aplikasinya jebol).

Jadi sebaiknya: pentester diberi akses pada kode yang belum obfuscated dan kode final yang sudah. Ini akan lebih optimal karena kerja pentester lebih cepat, tidak perlu membongkar obfuscation dan lebih aman (seluruh fungsi bisa ditest dengan baik). Ini dengan asumsi bahwa pentester memiliki keahlian untuk membaca kode dengan baik.

Semoga artikel singkat ini bisa memberikan gambaran mengenai apa itu code obfuscation dan berbagai batasannya.

Mendalami Bahasa C

Saya memberikan saran agar seseorang “mendalami bahasa C” jika ingin belajar reverse engineering. Ada pertanyaan menarik yang diajukan ke saya: sedalam apa belajarnya pak? apa yang harus dipelajari. Sesuai KBBI mendalami di sini berarti: meresapi; menyelami; mempelajari (menelaah, menyelidiki) dalam-dalam.

Saya tidak akan membahas dalam mengenai kenapa seseorang perlu memahami bahasa C, singkatnya: saat ini C masih dipakai di mana-mana, dan akan terus begitu untuk beberapa belas/puluh tahun mendatang. Kernel berbagai sistem operasi ditulis dalam C, berbagai library penting masih ditulis dalam C (library kompresi, enkripsi, image encoding/decoding, dsb), dan bahkan kebanyakan bahasa pemrograman lain diimplementasikan dalam C (misalnya Ruby, Python, dan PHP).

Di awal, pelajarilah dan pahamilah semua konsep dasar dalam bahasa C. Ini seharusnya tidak makan waktu lama. Bahasa C hanya punya beberapa tipe data dasar (void, char, short, int, long, float, dan double) masing-masing bisa signed atau unsigned. Kita bisa mendefinisikan sebuah konstanta dengan const. Tipe data lain adalah enum, union, dan struct (sudah pernah saya bahas di sini) semua tipe data bisa diberi nama dengan typedef.

Hanya ada beberapa sintaks loop (while, do while, dan for, semuanya dengan break dan continue) dan conditional (if/else, goto, dan switch/case/default). Sintaks pembuatan fungsi juga cukup sederhana, hanya perlu mengingat “return” untuk mengembalikan nilai.

Konsep manajemen memori dan string (array of characters) merupakan salah satu hal yang sering membuat pemula bingung. Jika Anda sudah berhasil membuat kode yang selalu lolos valgrind (artinya tanpa warning dan tanpa memory leak), maka Anda sudah lulus dalam pelajaran ini.

Berikutnya buatlah struktur data dalam bahasa C. Mulai dari yang sederhana seperti linked list. Setelah berhasil mencontek buku/website, cobalah menuliskan ulang struktur data tanpa mencontek. Seharusnya kalau sudah paham akan bisa. Lalu cobalah memakai fitur yang lebih rumit seperti function pointer. Pastikan ini juga lolos valgrind.

Cobalah memakai berbagai library C, bisa dimulai dari memakai berbagai fungsi di library C standar. Lalu diteruskan dengan library lain, misalnya zlib untuk kompresi data, expat untuk parsing XML, png untuk dekompresi file PNG.

Cobalah juga untuk memakai lingkungan yang berbeda. Sistem operasi yang berbeda, compiler yang berbeda, IDE yang berbeda. Supaya lebih paham yang mana yang merupakan bagian dari bahasa C, dan yang mana sekedar fitur IDE atau OS yang Anda pakai.

Menurut saya seseorang bisa dianggap cukup memahami bahasa C apabila sudah menyadari bahwa bahasa C itu sangat sederhana. Pertama yang harus disadari adalah ada bahasa C dan ada libray C.

Ketika belajar C, seseorang akan diberikan program “hello world”, seperti ini:

#include <stdio.h>

int main(int argc, char *argv){
   printf("hello world");
}

Tanpa menyadari apa itu gunanya include, dari mana printf berasal, dsb. Ketika baru belajar memang kita tidak perlu tahu itu semua, tapi jika ingin mendalami, kita harus mengerti peran: preprocessor, compiler, assembler, dan linker. Kita juga perlu memahami apa itu library, dan bagaimana membuat library sendiri (pernah saya bahas di sini).

Memprogram sistem embedded tanpa sistem operasi, misalnya microcontroller akan membuat kita sadar mengenai banyak hal yang mungkin tidak terpikirkan di desktop. Misalnya pernyataan sederhana

printf("hello world\n");

Di sistem embedded tanpa layar dan tanpa keyboard, mungkin Anda akan bertanya: ke mana outputnya? bagaimana kita membaca input?. Di sini akan disadari bahwa printf bukanlah bagian dari bahasa C, tapi bagian dari library C. Bahasa C bisa digunakan dengan berbagai library (di Linux saja ada pilihan: GNU LibC, diet libc, musl, dsb).

Di sinilah salah satu kelebihan bahasa C: ketika kita ingin tahu implementasi sebuah fungsi library apapun, kita bisa melihatnya dan biasanya dalam bahasa C juga (hanya sebagian yang memakai assembly). Beda misalnya dengan PHP: jika kita ingin tahu bagaimana fungsi strlen atau split di PHP diimplementasikan, maka yang harus kita baca adalah kode dalam bahasa C (bukan PHP).

Fungsi printf di atas sudah sangat kompleks untuk dijadikan contoh, jadi saya memakai fungsi sederhana saja: strlen. Ini implemenasi generik paling sederhana dari openbsd:

http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/lib/libc/string/strlen.c?rev=1.9&content-type=text/x-cvsweb-markup

size_t
strlen(const char *str)
{
	const char *s;

	for (s = str; *s; ++s)
		;
	return (s - str);
}

Tentunya ada ribuan cara untuk mengimplementasikan strlen. dan kadang untuk optimasi digunakan assembly. Saat ini banyak compiler C yang terbuka source codenya (GCC, Clang, Watcom, dsb) dan juga semua librarynya, jadi semua internal C bisa dipelajari.

Khusus untuk yang ingin belajar C untuk reverse engineering. Pelajarilah bahwa biasanya compiler C bisa menghasilkan kode assembly langsung dalam bentuk teks, dan kita bisa membandingkan kode C dengan kode assembly yang dihasilkan compiler. Sebagian informasi mengenai cara menghasilkan teks assembly bisa dibaca di sini.

Sebagai penutup. Jika Anda sudah bisa menganggap bahasa C sebagai bahasa yang sederhana maka kemungkinan Anda sudah mendapatkan pencerahan dan sudah mulai paham bahasa C.

Matematika dan Programming

Di kala senggang saya masih menjawab pertanyaan via Facebook/Email/Telegram dan banyak calon programmer yang sudah takut sebelum belajar programming: apakah akan butuh matematika? Jawabannya singkatnya tergantung. Tergantung ingin memprogram apa dan sedalam apa.

Matematika dasar tentunya sangat diperlukan, misalnya perkalian, pembagian, penjumlahan pengurangan. Hampir di semua bidang diperlukan ilmu dasar geometri. Misalnya tentang sistem koordinat ketika menggambar grafik (atau sekedar mengatur posisi teks di sebuah halaman web).

Pengetahuan dasar ini penting, dan ini berarti anak yang masih sangat kecil dan belum memiliki dasar matematika harus berhenti dulu belajar di titik tertentu. Ini pengalaman saya dalam mengajari anak saya ketika dulu mengajari dia programming di usia 4 tahun (sekarang sudah 7 tahun). Logika boolean juga perlu dipahami, sekedar AND, OR, dan NOT sudah cukup untuk sebagian besar kasus.

Untuk pemrograman grafik, terutama grafik 3D diperlukan pemahaman matriks dan vektor. Segala operasi matrix akan terlihat secara visual ketika memprogram grafik. Matrik dan vektor juga dipakai di Machine Learning. Jika fokusnya ingin mengolah data besar, maka berbagai ilmu matematika seperti: statistika, linear programming, graph, dan banyak konsep yang rumit akan terpakai.

Kadang hampir semua topik matematika terpakai dalam satu aplikasi. Contohnya untuk membuat game yang kompleks, diperlukan berbagai matematika untuk grafik (matriks, vektor), dibutuhkan AI untuk menggerakkan musuh (yang butuh matriks, vektor, statistika, dsb), dan untuk mencari jalan terpendek atau terbaik kadang dibutuhkan teori graph, dan masih banyak lagi komponen sebuah game.

Saya masih bisa memberikan banyak contoh lain aplikasi matematika dalam programming, tapi hal yang paling penting adalah: matematika (selain topik yang paling dasar) bisa dipelajari selagi kita belajar memprogram.

Sebelum masuk ITB, saya dulu belajar programming otodidak mulai dari SMP. Ketika saya belajar pemrograman sambil belajar matematika SMP/SMU, saya merasa lebih bisa mengerti karena bisa dicoba dalam bentuk program. Contohnya konsep fungsi dan komposisi fungsi. Saya bisa membuat sebuah fungsi di dalam bentuk kode program yang memanggil fungsi lain, dan saya bisa bereksperimen dengan itu. Jadi konsep “fungsi” tidak lagi menjadi hal yang abstrak.

Simbol summation (∑) di kode program hanyalah sebuah loop penjumlahan. Demikian juga dengan ∏ yang hanya loop dengan isi perkalian.

Demikian juga dengan topik matriks dan vektor waktu SMU. Saya mendapatkan banyak ilmu justru dari buku cara membuat game 3D yang saya baca dan saya coba waktu itu. Topik di kelas terasa sangat abstrak, tapi di kode program, bisa terlihat apa gunanya memiliki matriks dan berbagai sifat matriks lainnya.

Kalau Anda tertarik lebih dalam lagi mengenai berbagai topik matematika yang nantinya akan terpakai, silakan baca posting panjang ini (Math for Programmers) yang sudah ditulis seseorang di tahun 2006. Artikel tersebut juga membahas secara detail bagaimana belajar matematika yang lebih baik.

Jadi kesimpulannya adalah: belajar programming bisa dimulai asalkan sudah memiliki pengetahuan matematika yang dasar. Belajar konsep lanjut bisa dilakukan sambil belajar programming, dan bagi sebagian orang belajar dengan cara ini lebih mudah. Tapi jika Anda sudah punya dasar matematika yang bagus tentunya programming akan lebih mudah lagi dan Anda bisa membuat program yang lebih baik lagi. Contohnya: jika Anda punya dasar matematika yang bagus untuk AI, maka Anda bisa membuat game dengan AI yang lebih baik dari yang tanpa AI.

Signature email jaman kuliah

Dulu waktu kuliah, saya punya signature email seperti ini:

main(i){putchar((i-1)["Xme]i_l"]+(i++))&&(8-i)&&main(i);} 


Ternyata masih ada beberapa orang yang ingat, dan masih banyak yang penasaran apa artinya (cuma satu kata: Yohanes) dan  kok bisa muncul seperti itu?.

Pertama, menurut standar C lama, sebuah fungsi tanpa kembalian akan mengembalikan sebuah int, dan parameter yang tanpa tipe juga adalah sebuah int, jadi fungsi di atas sama dengan:

int main(int i){
   putchar((i-1)["Xme]i_l"]+(i++))
         &&(8-i)&&
           main(i);
 } 

Perhatikan juga bahwa di C, sifat operator && adalah short circuit, artinya dalam A() && B() jika A() mengembalikan false, maka B tidak dieksekusi:

#include <stdio.h>

int A() {
   printf("Fungsi A dipanggil\n");
   return 0;
}

int B() {
   printf("Fungsi B dipanggil\n");
   return 1;
}

int main(int argc, char *argv[])
{
   if (A() && B()) {
           printf("A dan B mengembalikan TRUE\n");
   }
   return 0;
}

Bagian main di atas sama saja dengan ini:

#include <stdio.h>

int main(int argc, char *argv[])
{
   if (A()) {
       if (B()) {
           printf("A dan B mengembalikan TRUE\n");
       }
   }
   return 0;
}

Jadi kode signature saya bisa dijadikan if juga seperti ini:

int main(int i){
   if (putchar((i-1)["Xme]i_l"]+(i++))) {
           if (8-i) {
               if (main(i)) {
               }
           }
   }
 } 

Perhatikan beberapa bisa diperjelas, misalnya (8-i) akan true jika (8-i) !=0, atau selama i != 8. Karena if (main()) kosong, maka bisa dihilangkan if-nya.

int main(int i){
   if (putchar((i-1)["Xme]i_l"]+(i++))) {
           if (i!=8) {
              main(i);
           }
   }
 } 

Di C, sebuah array adalah sebuah pointer dan sebuah string adalah array of characters. Di C:

    int *array = (int *)malloc(sizeof(int)*10);
    int index = 1;
    array[index] = 10;
    //syntax array access di atas sama dengan:
    *(array + index) = 10;
    //penjumlahan sifatnya komutatif
    *(index + array) = 10;
    //jadi ini juga sama:
    index[array] = 10; 

Untuk lebih jelasnya, string saya keluarkan, dan notasinya diperbaiki:

const char *str = "Xme]i_l";
int main(int i){
   if (putchar(str[i-1]+(i++))) {
           if (i!=8) {
              main(i);
           }
   }
 } 

Kita lihat fungsi putchar di manual:

fputc, fputs, putc, putchar, puts – output of characters and strings

Di bagian return value:

fputc(), putc() and putchar() return the character written as an unsigned char cast to an int or EOF on
error.

Catatan: karena fungsi putchar tidak dideklarasikan, dan saya tidak menginclude apapun, maka dianggap kembaliannya int, dan ada warning dari compiler.

Dalam kasus saya, putchar ini akan selalu mengembalikan 1, karena saya memprint satu karakter setiap waktu. Jadi kita sederhanakan lagi:

const char *str = "Xme]i_l";
int main(int i){
   putchar(str[i-1]+(i++));
   if (i!=8) {
      main(i);
   }   
 } 

Operator ++ (post increment) akan dilakukan setelah sebuah ekspresi, jadi dalam kasus ini bisa disederhanakan:

const char *str = "Xme]i_l";
int main(int i){
   putchar(str[i-1]+i);
   i++;
   if (i!=8) {
      main(i);
   }   
 } 

Sekarang ke bagian “magic”-nya. Di sistem operasi Windows, Linux atau POSIX yang lain, ketika program dijalankan, maka program dalam C akan menerima jumlah parameter dan isi parameternya

int main(int argc, char *argv[])

Jika kita deklarasikan tanpa argv, maka hanya jumlah parameternya yang kita dapatkan. Meskipun biasanya namanya argc dan argv, nama parameternya tentunya boleh apa saja

int main(int jumlah_argumen)

Jika program dijalankan tanpa parameter, maka jumlah argumennya adalah 1, yaitu nama program saat ini (yang tidak kita pedulikan di program ini). Jadi sebenarnya program tersebut dipanggil dengan

main(1);

Perhatikan bahwa “main” adalah sebuah fungsi di C, dan seperti fungsi apapun, bisa dipanggil bebas. Dalam kasus ini, saya memanggil (rekursif) main, dengan nilai i yang ditambahkan terus.

Jika ingin kode yang sangat jelas tiap langkahnya seperti ini:

#include <stdio.h>

const char *str = "Xme]i_l";
int main(int i){
   printf("\ni = %d\n", i);
   printf("str[i-1] = %c +%d = %c\n", str[i-1], i, str[i-1] + i);
   putchar(str[i-1]+i);
   i++;
   if (i!=8) {
      main(i);
   }   
} 

Dan outputnya:

[email protected]:~$ ./a.out 

i = 1
str[i-1] = X +1 = Y
Y
i = 2
str[i-1] = m +2 = o
o
i = 3
str[i-1] = e +3 = h
h
i = 4
str[i-1] = ] +4 = a
a
i = 5
str[i-1] = i +5 = n
n
i = 6
str[i-1] = _ +6 = e
e
i = 7
str[i-1] = l +7 = s

Demikian keisengan jaman kuliah dulu. Sekedar tambahan: dulu terinspirasi dari International Obfuscated C Code Contest, kode saya ini sangat sederhana dibandingkan para pemenang IOCCC.

Modifikasi Aplikasi Android

Ada banyak alasan kenapa kadang kita ingin memodifikasi aplikasi Android, dan ada banyak cara untuk melakukannya. Beberapa alasan saya pernah memodifikasi Android di antaranya: untuk pentest, untuk membuat aplikasi bisa berjalan lagi, dan untuk mencurangi game.

Untuk pentesting, modifikasi yang sering perlu dilakukan adalah: mematikan certificate pinning, mematikan root checking, dan juga menambahkan tracing untuk melihat logika aplikasi lebih jelas. Jam tangan murah dari China yang saya pakai memakai aplikasi Android untuk notifikasinya. Suatu saat aplikasi ini tidak bisa dipakai karena servernya di China sudah dimatikan, dengan mengubah aplikasinya, jam ini jadi tetap terpakai. Untuk masalah mencurangi game, sudah pernah saya tuliskan di blog saya yang lain.

Sekarang saya ingin menjelaskan beberapa cara untuk mengubah aplikasi Android. Saya tidak akan membahas aplikasi spesifik, karena tiap aplikasi berbeda, dan bahkan satu aplikasi yang sama bisa berubah total di rilis berikutnya. Asumsinya adalah aplikasi memakai kode Java/Smali (kebanyakan aplikasi seperti ini) dan bukan HTML/JS atau teknologi lain. Teknologi HTML/JS lebih mudah dimodifikasi, tapi beberapa yang lain seperti Unity (.NET), dan Rhomobile (Ruby) membutuhkan penanganan khusus .

Cara pertama adalah yang paling dasar, dan paling sering saya pakai: patching smali. Caranya adalah: APK diekstrak dengan apktool menjadi smali, di-edit, dicompile lagi, lalu disign. Kelebihan cara ini: hampir selalu berhasil, tidak perlu rooting HP dan menginstall software. Kekurangannya: tidak user friendly, harus mengedit kode smali yang cukup rawan salah. Dengan pendekatan ini, aplikasi asli perlu diuninstall, dan aplikasi baru menggantikan aplikasi lama.

Dengan unpack/repack APK, kita juga bisa menambah atau mengubah library native, termasuk juga menambahkan LD_PRELOAD seperti yang pernah saya bahas sebelumnya.

Pendekatan variasi lain dari cara tersebut adalah dengan DexPatcher. Ini sama saja dengan mengedit smali (aplikasi asli diuninstall lalu diinstall yang baru) tapi kita menuliskan kode patching dalam Java sehingga tidak rawan salah.  Tentunya selain cara di atas ada cara variasi lain. Contohnya untuk aplikasi tertentu hasil dekompilasi menjadi Java cukup baik sehingga mudah dikompilasi ulang.

Kelemahan kedua cara tersebut adalah: data lama aplikasi akan dihapus (kita perlu login ulang). Kelemahan lain adalah: signature aplikasi berubah. Beberapa aplikasi memiliki proteksi sehingga tidak akan mau berjalan jika signature aplikasi berubah, jadi bagian checking ini perlu dipatch juga. Kelebihan utama kedua cara di atas adalah: tidak butuh root, dan APK yang sudah diubah mudah disebarkan ke orang lain.

Pendekatan lain adalah dengan XPosed framework, yang sudah pernah saya bahas sebelumnya. Dengan pendekatan ini: signature aplikasi tidak berubah, kita membuat perubahan di luar aplikasi menggunakan bahasa Java. Kelemahan cara ini adalah: handphone harus terinstall XPosed framework. Tiap kali kita memodifikasi modul framework, handphone perlu direstart (bisa soft restart saja agar lebih cepat) untuk mengaktifkan modifikasinya.

Pendekatan sejenis XPosed yang lebih dinamik adalah Frida. Dengan Frida kita bisa memanipulasi berbagai hal secara dinamis menggunakan JavaScript, termasuk juga mengganti beberapa kode native. Jika ingin mudah, frida bisa dijalankan pada HP yang sudah diroot, dengan ini signature APK tidak berubah. Tapi jika tidak memungkinkan, kita bisa memodifikasi APK agar meload library Frida (modifikasi APK akan mengubah signature aplikasi).

Kelemahan Frida dibandingkan XPosed adalah: perubahan yang dilakukannya tidak permanen. Tapi kelemahan ini juga menjadi suatu kelebihan: modifikasi mudah dilakukan dan tidak perlu restart HP setiap kali

Contoh skrip Frida untuk salah satu soal Flare On

Selain XPosed dan Frida, sebenarnya ada juga Cycript dan MobileSusbtrate tapi keduanya sudah lama tidak diupdate dan tidak mendukung Android terbaru.

Semua cara di atas sebaiknya dikuasai, karena penerapannya tergantung kebutuhan. Contohnya: jika modifikasi hanya temporer (untuk pentest atau menyelesaikan CTF) mungkin Frida akan lebih baik. Jika ingin menyebarkan modifikasi tanpa menyertakan aplikasi asli (karena masalah hak cipta) maka XPosed bisa digunakan. Xposed juga cocok jika ingin membuat perubahan semi permanen.

Ketika ada sistem operasi baru, biasanya XPosed dan Frida tidak langsung bisa dipakai jika ada perubahan mendasar di sistem operasi, jadi tetap perlu tahu cara modifikasi manual.

Memprogram Apa Saja

Saya punya hobi memprogram benda apa saja dengan bahasa apa saja. Pertama saya contohkan dulu benda-benda yang saya program, lalu saya akan berusaha menjelaskan kenapa saya punya hobi ini, dan kenapa menyukai hobi ini.

Saya belajar memprogram otodidak waktu kelas 2 SMP dengan komputer Apple II/e dengan bahasa Basic. Waktu itu semuanya masih ngasal karena hanya belajar dari contoh source code. Waktu floppy disk-nya sudah error, saya tetap memprogram tiap hari, iseng membuat sesuatu, walau tidak bisa disimpan dan harus diketik lagi besoknya. Akhirnya berhenti memprogram benda itu setelah mati total.

Di SMU kelas 2 saya baru punya komputer lagi, kali ini IBM PC dengan Windows 95. Di PC tersebut saya memakai Pascal dan Assembly dan juga sudah mulai memprogram memakai Delphi.  Saya mencoba berbagai macam hal di PC (pernah saya tuliskan di sini). Dari sini sudah terlihat kalau saya sangat random, dari mulai memprogram utility sampai game. Apapun yang saat itu menarik buat saya.

Waktu kuliah saya membawa PC saya ke Bandung, tapi kos saya dibobol maling dan PC saya dicuri. Akhirnya saya sering nongkong di lab sampai tutup. Lab waktu tingkat satu dulu sangat memprihatinkan, cuma 486 DX (lebih rendah specnya dari PC pentium saya yang hilang), jadi di situ saya kembali mendalami memprogram mode teks.

Device non PC pertama yang saya program adalah Palm OS. Selanjutnya banyak device sejenis PDA/Smartphone yang saya program. Saya sempat membuat puzzle di PalmOS dan beberapa app kecil lain. Berikutnya adalah Symbian Bible yang sempat dipakai jutaan orang di masa kejayaan Symbian OS (menggunakan C++). Saya juga membuat Bible Plus untuk OS Blackberry (versi BBOS ditulis memakai Java dan versi BB10 di tulis dalam C++ menggunakan Qt).

Saya juga memprogram game console saya. Saya memporting dua aplikasi ke Wii, yaitu WiiApple dan Hatari (keduanya menggunakan C di platform PowerPC). Saya membuat aplikasi dadu di Nintendo DS ketika dadu anak saya hilang (menggunakan C di platform ARM).

11224430_10154009703648488_423812051884387581_n

Sebelum ada Apple Watch, Pebble, dan berbagai jam pintar Android, saya sudah memprogram jam EZ430-Chronos untuk menampilkan OTP (ini memakai C, prosessornya MSP430). Setelah punya jam pintar Android, saya juga membuat versi untuk Android (menggunakan Java).

10689684_10152985303908488_979245536455326491_n

Program yang saya buat kadang khusus cuma untuk mainan anak saya. Misalnya benda ini adalah Arduino dengan Joystick yang akan mengubah arah panah (dan jika ditekan tengahnya akan mengubah menjadi ikon lain).

11700914_10153778798908488_3986090072574211621_n

Ini dipakai di tempat tidur busnya:

11737831_10153778798868488_6085033335527012678_n

Sekedar lampu sirene untuk mobil-mobilan anak saya (menggunakan C di ATTiny13):

10945854_10153355982928488_8889515360750762989_o

Memprogram kereta mainanya (menggunakan C):

1521933_10152323796063488_1552795962_n

Atau mainan berbasis RFID untuk Jonathan yang bisa dilihat di sini (berbasis Python).

8993376562_55ff3846b3

Di mobil, saya memasang display yang terhubung dengan raspberry pi. Karena tidak menemukan media player yang mudah dipakai anak saya, maka saya membuat player sendiri yang dikendalikan dengan Joystick NES (ini menggunakan Python+PyGame).

11741108_10153783327303488_4463593811268542014_o

Kadang saya hanya membuat program kecil yang dipakai sendiri. Misalnya ini untuk belajar ESP8266 (menggunakan bahasa Lua).

10983178_10153384149983488_5592019281940390376_n

Saya juga tidak keberatan memakai berbagai bahasa. Misalnya untuk mainan Puzzle ini, saya memakai Haxe:

IMG_00000145_thumb

Kalkulator bisa diprogram dalam bahasa BASIC

Selain aplikasi yang sifatnya main-main, saya juga melakukan beberapa hal yang agak serius, seperti memporting kernel OS. Misalnya porting FreeBSD ke sebuah SoC ARM.

Di PC, saya memprogram dalam banyak bahasa: assembly, Pascal/Delphi, C, C++, PHP, Python, Lua, Ruby, Java, JavaScript dsb. Sebagian besar yang saya tulis pernah jadi produk atau dipakai di internal perusahaan.

Bagi saya memprogram dengan berbagai teknologi memiliki kepuasan tersendiri. Ketika sebuah program bisa berjalan bagi saya itu sudah merupakan hal yang ajaib. Meskipun saya tahu bagaimana komputer bekerja sampai level bit processing dan memahami teori turing completeness, tetap saja magic.

Beberapa pengalaman terasa lebih magic dibanding sebelumnya. Kali pertama memprogram socket, rasanya takjub dua buah program di dua komputer bisa berinteraksi,  kali pertama memprogram skrip CGI (common gateway interface): wah bisa bikin situs yang dinamis, kali pertama memprogram LED yang bisa berkedip, menggerakkan motor, dan membaca sensor rasanya senang sekali sebuah program bisa berinteraksi langsung dengan dunia fisik.

Pengalaman programming juga membantu saya memahami banyak konsep matematika. Mengenal konsep fungsi di programming membuat saya lebih mudah memahami topik seperti komposisi fungsi. Belajar perkalian matriks ketika membuat game 3D lebih nyangkut di kepala saya daripada ketika belajar di kelas.

Banyak orang menggunakan berbagai alat musik untuk memainkan lagu tertentu. Sebenarnya lagunya sama saja, tapi ada kepuasan tersendiri mendengarkan musik menggunakan alat musik alternatif (misalnya ada yang memainkan Let It Go memakai Game Boy). Sama halnya dengan program: emulator Apple yang berjalan di PC sama saja dengan yang berjalan di Wii, tapi ada kepuasan tersendiri melihat program yang sama bisa berjalan di device yang berbeda. Dalam kasus emulator Apple, Wii bisa terhubung ke TV di ruang tamu dan bisa membangkitkan nostalgia dulu orang-orang memakai komputer Apple dengan monitor TV sebagai displaynya.

Kadang saya menganggap berbagai teknik yang sudah lama tak akan terpakai lagi, dan ternyata saya salah. Dulu waktu ingin membuat aplikasi berjalan di latar belakang di DOS, saya memakai mengintercept interrupt 1ch, supaya dipanggil 18.2 kali per detik. Waktu saya belajar Linux dan Windows dan kemampuan multiprocess dan multithreading, saya pikir: wah ini cara yang mudah dan enak. Tapi ketika berhadapan dengan browser: kembali ke single threading, dan beberapa hal perlu disimulasikan dengan timer. Saat ini support WebWorker sudah memungkinkan multi threading tapi tetap terbatas (contoh: akses DOM tidak bisa dari web worker, jadi processing DOM tetap harus single thread).

Meski sudah saya jelaskan, saya sadar bahwa banyak hobi yang sulit dimengerti oleh orang lain. Selalu ada counter argument kenapa hobi itu “aneh”, atau “nggak berguna”.

Contoh: hobi mendaki gunung. Penjelasannya kenapa seseorang bisa suka: mengenai perjalanannya (it’s all about the journey), mengenai udara segarnya, mengenai pemandangan indahnya. Contoh counter argument-nya: kan capek jalan naik gunung, saya baca naik gunung itu bahaya (udah banyak orang yang meninggal gara-gara tersesat), nanti cari toilet di mana? pemandangannya kan bisa dicari di Google, di taman juga udaranya segar. Saya ambil pake tour aja yang di antar bus ke puncak gunung yang lain, sama aja kan? terus kenapa harus naik gunung yang lain? bukannya pemandangannya mirip-mirip aja?

Mungkin bagi orang awam berbagai kamera sama saja, lensa yang mahal versus mahal banget tidak terlihat bedanya. Dan masih banyak lagi hal-hal lain yang cuma bisa diapresiasi pemilik hobi.

Hanya karena saya suka memprogram berbagai device tidak membuat saya jadi jago/pintar memprogram, hanya membuka pikiran saya lebih luas. Sama seperti orang yang hobi naik gunung tidak membuat orang tersebut jadi ahli, walaupun mungkin dia lebih sehat dari aktivitas mendaki dibanding yang diam saja di rumah. Dari hobi saya, saya tahu sedikit lebih banyak dari mereka yang tidak punya hobi programming.

Tulisan ini cuma sekedar sharing hobi aneh saya karena saya masih jarang ketemu orang yang senang memprogram apa saja. Siapa tahu bisa menarik orang lain untuk hobi yang sama.

Memahami Static dan Shared Library di Linux

Saya masih sering melihat programmer C dan juga administrator yang bingung dengan konsep shared library. Shared library adalah file berisi kode yang bisa diload saat program dieksekusi (runtime). Karena diload pada runtime, maka sebuah shared library bisa digunakan oleh lebih dari satu program.

Penjelasan mengenai static dan shared library biasanya membingungkan, jadi di posting ini saya akan menjelaskan dengan banyak contoh. Sebenarnya hampir semua contoh di tulisan ini berlaku juga untuk lingkungan POSIX lain selain Linux, tapi saya hanya mencoba kode ini di Linux 64 bit dengan compiler gcc. Di balik layar, program gcc sebenarnya akan memanggil berbagai program lain (preprocessor, assembler, linker) tergantung pada parameter yang kita berikan tapi agar penjelasannya sederhana, saya akan memakai gcc saja dan tidak akan menjelaskan apa yang terjadi di balik layar.

Kode monolitik

Kita mulai dari kode yang sangat sederhana seperti ini:

/*file: main.c */
#include <stdio.h>

double operation(double a, double b)
{
        printf("Plus operation\n");
        return a+b;
}

int main(int argc, char *argv[])
{
        double a = 5;
        double b = 3;
        printf("Result of operation (%.2f, %.2f) is: %.2f\n", a, b, operation(a, b));
        return 0;
}

Karena semua sudah ada di satu file, maka ini bisa dikompilasi dan jalankan langsung.

gcc main.c -o main

Memecah source code

Di sini ada satu fungsi bernama operation yang hanya melakukan operasi sangat sederhana. Anggap saja fungsi ini rumit dan penting dan ingin kita pisahan agar bisa dipakai oleh orang lain. Sekarang operation saya pindahkan ke operation.c

/*file: operation.c */
#include <stdio.h>

double operation(double a, double b)
{
        printf("Plus operation\n");
        return a+b;
}

Dan kode main menjadi:

/*file: main1.c */
#include <stdio.h>

int main(int argc, char *argv[])
{
        int a = 5;
        int b = 3;
        printf("Result of operation (%d, %d) is: %d\n", a, b, operation(a, b));
        return 0;
}

Jika kita coba compile main.c saja, seperti ini:

gcc main.c -o main

maka akan ada warning DAN error. Isi errornya adalah:

/tmp/cc08zl1h.o: In function `main':
main2.c:(.text+0x45): undefined reference to `operation'
collect2: error: ld returned 1 exit status

Ini karena compiler tidak bisa menemukan implementasi dari fungsi operation. Kita perlu memberikan file operation.c

gcc main1.c operation.c -o main

Sekarang kompilasi berhasil, tapi tetap ada peringatan:

implicit declaration of function ‘operation’

Compiler C tidak tahu menahu mengenai fungsi bernama operation. Secara default (karena alasan sejarah), compiler akan menganggap fungsi tersebut mengembalikan sebuah int

Jika kita coba jalankan:

Plus operation
Result of operation (5.00, 3.00) is: 0.00

Hasilnya 0.00 karena fungsi operation dianggap mengembalikan int. Ini bisa diperbaiki dengan menambahkan deklarasi fungsi sebelum main:

/*file: main2.c */
#include <stdio.h>

/* INI YANG DITAMBAHKAN */
double operation(double a, double b);

int main(int argc, char *argv[])
{
        int a = 5;
        int b = 3;
        printf("Result of operation (%d, %d) is: %d\n", a, b, operation(a, b));
        return 0;
}

Sekarang kita coba lain:

$ gcc main2.c operation.c -o main

Dan berhasil:

Plus operation
Result of operation (5.00, 3.00) is: 8.00

Object code

Tapi sekarang kita tidak ingin orang mengetahui source code operation.c, kita bisa menjadikan operation.c menjadi object code:

$ gcc -c operation.c

Hasilnya adalah file operation.o. Ini bisa dikirimkan ke orang lain yang memiliki main.c dan kompilasi bisa dilakukan dengan (perhatikan: operation.c diganti menjadi .o)

$ gcc main2.c operation.o -o main

Tapi ada satu masalah di sini: penerima operation.o tidak tahu fungsi apa di dalam operation.o dan apa parameternya. Sebenarnya nama fungsinya saja bisa dilihat dengan nm atau objdump tapi apa parameternya tidak bisa. Contoh dengan nm:

$ nm operation.o
                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 T operation
                 U puts

File Header

Untuk memudahkan, kita perlu membuat file header: operation.h, isinya bisa seperti ini saja:

double operation(double a, double b);

Dan di file yang memakai fungsi tadi, bisa menggunakan include:

/*file: main3.c */
#include <stdio.h>
/* INI YANG DITAMBAHKAN */
#include "operation.h"

/*ini tidak lagi diperlukan, karena sudah diinclude dari operation.h*/
/*double operation(double a, double b);*/

int main(int argc, char *argv[])
{
        int a = 5;
        int b = 3;
        printf("Result of operation (%d, %d) is: %d\n", a, b, operation(a, b));
        return 0;
}

Sekarang kita sudah bisa mengirimkan file operation.h dan operation.o untuk dipakai oleh seseorang. Orang tersebut tidak tahu implementasinya (kecuali dengan dekompilasi), dan bisa memakai fungsinya dengan mudah. Perlu dicatat bahwa ada beberapa standar file object, secara umum: file dari compiler yang sama akan kompatibel, tapi tidak dijamin kompatibel dengan kode dari compiler yang berbeda.

Biasanya file header tidak hanya berisi deklarasi fungsi, tapi juga deklarasi struktur, misalnya seperti ini:

struct point {
int x, y;
};

void init_point(struct point *p);

Jika file ini di-include sekali saja, maka tidak ada masalah. Tapi jika tidak sengaja diinclude dua kali, maka akan ada error:

 error: redefinition of ‘struct point’

Bagaimana mungkin menginclude “tidak sengaja” dua kali (atau lebih?). Contohnya ada file circle.h menginclude file point.h, dan file square.h juga menginclude point.h. Jika program utama menginclude circle.h dan square.h maka point.h akan diinclude dua kali. Masalah ini sering muncul sehingga ada konvensi membuat header seperti ini:

#ifndef POINT_H
#define POINT_H
struct point {
int x, y;
};

void init_point(struct point *p);
#endif

Ketika file diinclude kali pertama, semuanya akan normal dan POINT_H akan terdefinisi. Jika diinclude lagi, maka tidak akan terjadi apa-apa karen POINT_H sudah terdefinisi.

Sekarang ada masalah baru. Andaikan kita ubah operation.c sehingga menggunakan int, tapi lupa mengubah header operation.h

/*file: operation.c */
#include <stdio.h>

int operation(int a, int b)
{
        printf("Plus operation\n");
        return a+b;
}

Maka semuanya akan berhasil dicompile seperti semula, tapi hasilnya tidak seperti yang diharapkan. Untuk mengatasi ini, kita sebaiknya menginclude header di implementasi, seperti ini:

/*file: operation.c */
#include <stdio.h>
/* BARIS INI DITAMBAHKAN */
#include "operation.h"

int operation(int a, int b)
{
        printf("Plus operation\n");
        return a+b;
}

Sekarang jika deklarasi dan implementasi tidak konsisten akan mucul error:

operation.c:5:5: error: conflicting types for ‘operation’
 int operation(int a, int b)
     ^~~~~~~~~
In file included from operation.c:3:0:
operation.h:4:8: note: previous declaration of ‘operation’ was here
 double operation(double a, double b);

Static Library

Sekarang header kita sudah bagus. Tapi ada masalah lain: jika hanya ada satu file saja, maka satu file objek sudah cukup. Tapi jika ada banyak file objek, ini akan merepotkan. Contohnya saja, jika kita memiliki kode untuk menangani bentuk, dan menggunakan banyak objek seperti: circle.o, square.o, polygon.o, dsb, maka kompilasinya akan cukup repot (perintahnya menjadi panjang). Berbagai file objek bisa disusun jadi satu library statik untuk memudahkan kompilasi.

Kita bisa menyusun satu file saja:

ar rcs libop.a operation.o

Atau banyak file

ar rcs libop.a operation.o operation2.o

Progam “ar” akan menciptakan arsip library, dengan flag “c” untuk membuat library (create) jika belum ada, “r” untuk menggantikan (replace) file operation.o jika sudah ada di arsip dan “s” untuk membuat indeks. Kita bisa melihat isi arsip .a dengan parameter “t” (test):

ar t libop.a

Sudah menjadi konvensi untuk menamai suatau library statik dengan prefiks lib dan dengan suffiks a. Kita bisa memakai library ini ketika mengkompilasi dengan menggunakan opsi l diikuti nama library tanpa prefix lib (cukup:op saja)di gcc:

gcc main3.c -lop

Tapi ini akan error:

/usr/bin/ld: cannot find -lop
collect2: error: ld returned 1 exit status

Karena defaultnya gcc hanya mencari di path library. Path standar ini bisa dilihat dengan:

$ gcc -v -Wl,verbose 2>&1|grep LIBRARY

Di komputer saya outputnya seperti ini:

LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../:/lib/:/usr/lib/

Intinya: kita bisa memindahkan file libop.a ke salah satu direktori tersebut, atau cukup beritahu path tempat library kita berada. Karena berada di direktori saat ini, kita bisa memakai titik tunggal (.):

gcc main3.c -L. -lop -o main

Sekarang program berhasil dilink dengan library, dan berjalan seperti biasa. Sebagai catatan, sebenarnya gcc juga mengijinkan kita langsung menyebut nama file librarynya, walau biasanya ini jarang dilakukan

gcc main3.c libop.a -o main

Andaikan kita salah melink ke library yang tidak mengandung implementasi fungsi operation, maka akan muncul error:

main.c:(.text+0x40): undefined reference to `operation'
collect2: error: ld returned 1 exit status

Ketika kita menggunakan static library, maka kode dari library tersebut di “copy paste” ke file executable. Untuk menjalankan program kita, kita tidak butuh lagi libop.a karena kodenya sudah ada di executable. Jika ada program lain yang memakai library yang sama, program itu mengandung kode yang sama (duplikat).

Andaikan kita mengupdate operation.c dengan versi yang lebih baik (misalnya ada perbaikan bug, atau ada optimasi baru) maka kita harus mengcompile ulang program kita agar mendapatkan perbaikan tersebut.

Shared Library

Dalam kasus tertentu kita ingin agar hanya ada satu kode saja yang dipakai bersama. Misalnya sebuah XML parser dipakai oleh banyak program, jika ada update pada parser XML kita tidak ingin mengkompilasi ulang seluruh program yang memakai library tersebut. Dalam kasus ini kita sebaiknya menggunakan shared library. Satu library yang bisa dipakai banyak program.

Untuk mengcompile operation.c menjadi shared library bisa dilakukan dengan:

gcc -fPIC -shared operation.c -o libop.so

Cara kompilasi juga sama dengan static library (jika ada libop.a dan libop.so, maka yang .so diprioritaskan):

gcc main3.c -L. -lop -o main

atau:

gcc main3.c libop.so -o main

Tapi jika kita coba jalankan:

$ main
./main: error while loading shared libraries: libop.so: cannot open shared object file: No such file or directory

Kode dari libop.so tidak disalin ke executable dan masih ada di libop.so, jika kita ingin menjalankan kita harus melakukan salah satu dari ini: menyalin libop.so ke path library sistem, menambahkan path system baru (bisa mengedit /etc/ld.so.conf) atau memberikan pathnya dengan LD_LIBRARY_PATH.

Untuk melihat pencarian yang dilakukan oleh sistem:

$ ldconfig -v 

Jika kita mengubah file /etc/ld.so.conf, kita perlu menjalankan ldconfig sebagai root untuk mengupdate cache.

Cara termudah tanpa akses root adalah menambahkan ke environment LD_LIBRARY_PATH (titik adalah direktori saat ini, bisa saja diganti path lain, misalnya /home/yohanes/mylibs).

$ export LD_LIBRARY_PATH=. 
$ ./main
Plus operation
Result of operation (5.00, 3.00) is: 8.00

Cara di atas akan membuat semua perintah berikutnya menggunakan path library yang baru. Jika kita hanya sekedar ingin mengubah path untuk satu perintah saja, maka cara ini lebih baik:

$ LD_LIBRARY_PATH=. ./main
Plus operation
Result of operation (5.00, 3.00) is: 8.00

Fleksibilitas shared library

Sekarang kita lihat fleksibilitas shared library dengan membuat shared library baru dengan nama sama. Saya membuat file baru: operation-mult.c yang mengalikan operand.

#include <stdio.h>

double operation(double a, double b)
{
	printf("Multiply operation\n");
	return a*b;
}

Agar lebih jelas, saya masukkan file ini dalam direktori “op-mult”. Lalu saya compile seperti ini:

gcc -fPIC -shared op-mult/operation-mult.c -o op-mult/libop.so

Sekarang jika saya jalankan program sebelumnya, tapi dengan path libop.so yang berbeda:

$ LD_LIBRARY_PATH=./op-mult ./main
Multiply operation
Result of operation (5.00, 3.00) is: 15.00

Output program berubah: dari penjumlahan menjadi perkalian. Jika kita memiliki lebih dari satu library dengan nama yang sama di path yang berbeda, maka library yang ditemukan pertama akan diload

$ LD_LIBRARY_PATH=./op-mult:. ./main
Multiply operation
Result of operation (5.00, 3.00) is: 15.00
[/code]

Bagaimana jika kita punya file yang kebetulan namanya sama, tapi tidak memiliki fungsi yang kita butuhkan? Hasilnya akan error:

./main: symbol lookup error: ./main: undefined symbol: operation

Override Fungsi dengan LD_PRELOAD

Ada trik menarik yang bisa dilakukan dengan menggunakan shared library, yaitu mengganti implementasi fungsi lain dengan fungsi milik kita sendiri. Trik ini bisa dipakai untuk runtime patching.

Dengan menggunakan program ltrace kita bisa melihat fungsi library apa yang dipanggil oleh sebuah program. Untuk mudahnya, kita akan memakai program paling pertama, yang tidak memakai library, versi monolitik:

ltrace ./main
puts("Plus operation"Plus operation
)                                                = 15
printf("Result of operation (%d, %d) is:"..., 5, 3, 8Result of operation (5, 3) is: 8
)                = 33
+++ exited (status 0) +++

Ada 2 call yang dibuat oleh program tersebut: puts dan printf. Sebenarnya di dalam program kita memakai printf saja, tapi compiler mengoptimasi printf tanpa parameter tambahan menjadi puts saja. Jika kita meminta compiler untuk tidak menghapus file assembly dengan opsi -S

$ gcc -S main.c -o main

Maka kita bisa melihat bahwa dalam operation memang digunakan puts sedangkan dalam main digunakan printf:

Cara lain melihat ini adalah dengan objdump -d nama_executable untuk melihat kodenya.

Untuk mudahnya sekarang kita ingin mengganti puts agar melakukan hal lain: mencetak string “EXTRA” sebelum mencetak string yang seharusnya.

/*file: myputs.c*/

#define _GNU_SOURCE  
#include <dlfcn.h>

int (*orig_puts)(const char *s);

int puts(const char *s)
{
        if (orig_puts == 0) {
                orig_puts = dlsym(RTLD_NEXT, "puts");
        }
        orig_puts("EXTRA: ");
        return orig_puts(s);
}

Lalu compile filenya sebagai shared library, tapi sertakan juga libdl (untuk fungsi dlsym).

$ LD_PRELOAD=./libmyputs.so ./main-standalone 
EXTRA: 
Plus operation
Result of operation (5, 3) is: 8

Dalam kode pengganti milik kita, pertama yang dilakukan adalah mencari alamat fungsi “puts” yang asli, lalu meletakkannya di variabel bernama orig_puts (tiipenya adalah sebuah function pointer). Setelah itu kita bisa memanggil fungsi aslinya. Tentunya kita tidak harus memanggil fungsi aslinya jika memang ingin mengganti seluruhnya.

Beberapa kegunaan LD_PRELOAD ini misalnya: untuk membetulkan bug tanpa mengubah program. Kadang ini juga bisa digunakan untuk testing, jika sebuah program memakai random, lalu crash hanya jika nilai tertentu dihasilkan oleh random, maka kita bisa menggunakan LD_PRELOAD untuk mengganti fungsi random agar nilai kembaliannya sebuah nilai yang tetap.

LD_PRELOAD Ini juga bisa digunakan untuk cracking program, misalnya jika program hanya bisa berjalan hanya sebelum expiration date, maka kita bisa membuat fungsi time() yang selalu mengembalikan tanggal tertentu.

Penutup

Pemahaman mengenai library bisa membantu menyelesaikan banyak masalah dan tidak perlu coba-coba yang memakan banyak waktu. Semoga posting ini cukup menjelaskan seluk beluk library di Linux.

Mengapa memakai bahasa pemrograman tertentu?

Mengapa seseorang memilih suatu bahasa tertentu untuk menyelesaikan suatu masalah atau membuat aplikasi tertentu? Ternyata jawabannya bisa banyak.  Kebanyakan pilihan intinya adalah keterpaksaan dan terakhir baru faktor kenyamanan.

Keterpaksaan pertama dari non teknis, misalnya dari permintaan atasan atau permintaan client. Hal ini sering kali tidak bisa ditawar lagi, terutama jika sudah melibatkan kontrak legal. Paksaan ini sering menghasilkan kode yang aneh atau tidak menggunakan fitur yang tepat dari sebuah bahasa, karena programmer dipaksa menggunakan bahasa lain, dan karena harus buru-buru mereka akan menggunakan gaya bahasa X di bahasa Y.

Sebuah teknologi tertentu kadang hanya bisa diprogram dengan satu bahasa. Misalnya dulu ponsel cuma bisa diprogram dengan Java (J2ME) jadi ya terpaksa harus memakai bahasa Java. Microcontroller tertentu perlu memakai assembly karena ukuran ROMnya sangat kecil dan tidak ada compiler C yang bisa menghasilkan kode sekecil itu.

Sekarang ini jumlah memori dan kecepatan berbagai device sudah sangat tinggi, sehingga memungkinkan interpreter berjalan di platform apa saja. Ini memberi kebebasan memilih bahasa. Contohnya: dulu microcontroller CPU dan RAMnya sangat kecil sehingga tidak cukup untuk menjalankan interpreter Python. Sekarang sudah banyak microcontroller yang bisa menjalankan MicroPython.

Bahasa tertentu dipilih karena masalah kinerja (performance). Meskipun Python bisa berjalan di microcontroller, tapi kecepatannya jauh lebih lambat dibandingkan C/C++ (karena interpreted). Faktor kecepatan ini sangat terasa untuk keperluan tertentu, misalnya untuk membuat animasi lampu yang jumlahnya banyak. Di dalam konteks lain juga sama: bahasa tertentu dipilih karena masalah performance.

Berbagai bahasa memiliki tingkat strictness yang berbeda, dan mulai masalah sintaks sampai tipe data. Sebagian bahasa memungkinkan kecerobohan yang sulit dilakukan di bahasa lain. Contoh sederhana, dalam JavaScript kita bisa membuat kode seperti ini dan akan berjalan, walau hasilnya mungkin tidak seperti yang diharapkan.

Tipe a adalah Number dan b adalah String, tapi keduanya bisa dioperasikan

Pemula Ruby yang beralih dari bahasa tertentu (misalnya C) mungkin akan bingung dengan sifat Ruby di mana 0 dianggap true. Alasannya: 0 adalah object yang valid, dan nilainya dianggap true.

Untuk bahasa yang sangat dinamik, dibutuhkan banyak test case untuk banyak hal sederhana, sedangkan di bahasa yang tipenya dicek oleh compiler, jumlah test bisa dikurangi. Ada bahasa yang lebih strict lagi, misalnya bahasa ADA yang sempat jadi bahasa wajib oleh Department of Defense Amerika. Secara umum: bahasa tertentu mengurangi kesalahan programmer (baik dari syntax, dari compiler), sehingga bahasa tersebut dipilih untuk tujuan tertentu.

Beberapa masalah mudah diselesaikan menggunakan library yang sudah ada. Ketersediaan library ini menjadi alasan kenapa memilih sebuah bahasa. Misalnya sudah ada library OpenCV (Open computer vision) untuk memproses video secara realtime (misalnya face recognition). OpenCV ditulis dalam C++ dan  ada banyak binding sehingga fungsi-fungsi C++ ini bisa diakses dari bahasa lain (misalnya Python, Java, C#, NodeJS) tapi tidak semua binding ini sempurna, dan sebagian ketinggalan versinya. Misalnya saat tulisan ini dibuat, binding OpenCV untuk JavaScript/NodeJS versi 3 belum ada.

Jika tidak ada batasan keterpaksaan, maka alasan berikutnya adalah kenyamanan. Beberapa aplikasi bisa ditulis dalam bahasa apapun karena tidak tidak keterpaksaan tertentu. Dalam kasus seperti ini, orang bisa memilih bahasa sesuai dengan familiaritas dengan bahasa tersebut. Sebagian programmer yang menguasai banyak bahasa akan memilih bahasa yang ternyaman (dan kalau bisa yang terbaik) untuk menyelesaikan suatu masalah.

Jangan heran kalau dalam sebagian masalah, C bisa lebih nyaman dari Java atau bahasa lain.  Contoh kecil: sering kali dalam memprogram hardware kita membutuhkan array of bytes (misalnya Protocol Data Unit pada NFC). Inisialisasi array of byte di Java jika nilainya kurang dari 127 bisa dilakukan seperit ini:

byte b [] = new byte[] {1, 127};

Tapi jika nilainya lebih dari 127 tiba-tiba kodenya tidak bisa dicompile:

byte b [] = new byte[] {1, 128};

Dengan error:

incompatible types: possible lossy conversion from int to byte

Cara yang benar adalah dengan menggunakan casting ke byte:

byte b [] = new byte[] {1, (byte)128};

Sedangkan di C, dengan tipe uint8_t (dari header stdint.h) kita tidak perlu memperdulikan hal kecil seperti itu. C juga mendukung bit fields yang bisa membuat manipulasi bit tidak diperlukan (karena dilakukan oleh compiler).

Di sisi lain: membuat aplikasi web lebih sulit dilakukan dengan C. Bereksperimen dengan bilangan integer yang besar (ratusan digit, misalnya untuk operasi RSA) jauh lebih mudah dilakukan di Python dibandingkan Javascript (big integer sudah built in di Python).

Perlu dicatat bahwa bahasa pemrograman yang populer juga masih terus berkembang. Dulu untuk melakukan iterasi list di Java butuh kode yang cukup panjang, tapi kemudian syntax for ditambahkan yang memudahkan ini. Ini hanyalah syntactic sugar alias pemanis saja, di dalamnya implementasinya masih sama. Sebagian bahasa memiliki sintaks yang sudah manis dari sejak dirancang sehingga kadang seseorang lebih memilih bahasa lain.

Kadang tool yang tersedia untuk sebuah bahasa juga menjadi faktor penting dalam memilih sebuah bahasa. Tools ini bisa berupa: compiler, debugger, IDE, dll. Compiler yang lambat bisa membuat frustrasi, jika debugger tidak tersedia akan membuat development lebih lama, IDE yang jelek bisa memperlama coding (apalagi jika sering error/hang) tapi IDE yang bagus bisa mempercepat coding.

Semoga sekarang cukup jelas kenapa ada banyak bahasa, kenapa seseorang perlu mempelajari banyak bahasa, dan kenapa bahasa tertentu dipilih untuk menyelesaikan masalah tertentu.

XPosed: Framework sakti untuk modifikasi Android

Topik kali ini agak advanced, tapi juga pratis. XPosed  adalah sebuah framework open source yang memungkinkan kita membuat modul untuk memodifikasi sistem dan aplikasi Android yang ditulis menggunakan Java. Dari sudut pandang programming, framework ini menarik karena memungkinkan kita menambahkan dan mengintersepsi kode pada aplikasi closed source Android, sedikit mirip Aspect Oriented Programming.

Screenshot_2016-08-21-07-33-48.png

XPosed bekerja dengan memodifikasi runtime Android (Dalvik/Art) sehingga menjadi mungkin untuk memanggil kode custom di awal atau akhir sebuah method apapun. Perhatikan bahwa yang dimodifikasi adalah runtime Android saja, jadi hanya kode yang ditulis dalam Java dan diinterpretasikan oleh Dalvik atau Art runtime saja yang bisa diintersepsi, kode native tidak bisa. Ada framework lain, misalnya Cydia yang bisa mengintersepsi kode native juga, tapi Cydia versi Android ini agak lambat perkembangannya (saat ini belum mendukung Android 5 dan 6).

Untuk memakai XPosed, yang harus Anda lakukan adalah: menginstall recovery alternatif (CWM, TWRP, dsb) dan menginstall frameworknya (tergantung ponsel Anda, ini mungkin bisa membatalkan garansi). Anda juga perlu menginstall XPosed installer yang akan memanage modul mana saja yang aktif.

Mari kita mulai dengan contoh yang praktis tentang intersepsi kode. Jika kita punya kode seperti ini:

Kita bisa mengintersepsi ini dengan mengembalikan agar isValidLicense selalu menjadi true. Kita juga bisa memodifikasi agar  validateLicense segera kembali (tidak melakukan apapun, jadi tidak akan melempar exception). Dalam kasus yang sebenarnya, kita harus teliti, misalnya mungkin di dalam ada validateLicense kode lain untuk mensetup versi premium jika license valid (jadi tidak bisa kita patch agar selalu kembali).

Dalam contoh di atas method checkRoot melakukan pemeriksaan apakah device di-root oleh pengguna, dan cara memeriksanya adalah dengan menggunakan XML yang berisi daftar command dan package yang harus diperiksa. Dalam kasus seperti ini (ini penyederhanaan dari kasus nyata yang saya temui), maka kita bisa juga mengganti parameter input XML menjadi XML kosong.

Berikut ini contoh kode untuk mem-patch agar isValidLicense selalu mengembalikan true.

Beberapa baris pertama hanya mengimpor package yang kita butuhkan. Berikutnya kita perlu mengimplementasikan IXposedHookLoadPackage. Di dalam handleLoadPackage, kita bisa memfilter untuk package aplikasi mana saja hooking ini akan kita lakukan. Dalam kasus ini saya hanya tertarik pada satu aplikasi saja. Dalam kasus tertentu kita ingin menghook semua aplikasi, misalnya kita bisa membuat modul untuk melakukan SSL Unpinning untuk semua aplikasi.

Berikutnya yang harus kita ketahui adalah signature dari method yang akan kita hook. Untuk aplikasi yang sourcenya terbuka, atau untuk aplikasi yang mudah didekompilasi karena tidak diobfuscate, hal ini mudah dicari. Jika aplikasi di-obfuscate, hal ini agak lebih sulit. Dalam contoh ini package yang dimaksud adalah es.yohan.example dan methodnya adalah isValidLicense yang memiliki parameter berupa String.

Ada dua hook yang tersedia: beforeHookedMethod dan afterHookedMethod. Dalam beforeHookedMethod, kita bisa mengubah parameter yang masuk ke program (atau bisa juga sekedar melakukan logging atau mengirimkannya ke tempat lain), dan dalam afterHookedMethod kita bisa menerima dan atau mengubah nilai kembalian method. Dalam contoh di atas, saya hanya mengeset agar hasilnya selalu true.

Mungkin sekarang Anda bisa melihat kelemahan XPosed: modifikasi hanya bisa dilakukan di awal dan di akhir method. Jika Anda ingin memodifikasi sesuatu di tengah-tengah, misalnya mengubah konstanta yang tertanam di logika program (misalnya if (x>2127)), atau sekedar mengubah “kurang dari” menjadi “lebih dari” maka sulit melakukannya dengan XPosed.

Jika masih ingin mengubah detail sebuah method dengan XPosed, caranya adalah dengan menulis ulang method itu, dan di beforeHookedMethod kita memberitahu XPosed framework agar tidak memanggil method aslinya. Dalam kasus tertentu lebih mudah mengedit file APK-nya.

Secara praktis apa saja yang bisa dilakukan dengan kemampuan XPosed ini? Sudah banyak orang menuliskan modul untuk Xposed, dari mulai sekedar mengubah kosmetik Android (mengubah tampilan ini dan itu), menambahkan fitur tertentu (misalnya menambahkan Zoom ke aplikasi Instagram), memblokir iklan di berbagai aplikasi (termasuk juga Youtube yang sekarang memasukkan iklan bukan hanya di awal video tapi juga di tengah video jika videonya panjang), memperbaiki offset GPS di China, dan masih banyak lagi.

Ada juga modul untuk meningkatkan privasi (misalnya XPrivacy dan PMP), dengan memblok berbagai request dari aplikasi Android yang nakal atau mengembalikan data palsu. Contoh: banyak sekali aplikasi yang meminta akses ke contact list, ternyata mengupload seluruh phonebook kita ke server mereka (dengan berbagai alasan, misalnya untuk fitur referal, dsb).

Penghematan batere juga bisa dilakukan, misalnya dengan memblok aplikasi agar tidak bisa auto start, membatasi akses sensor, dsb.

Kebanyakan modul di repository XPosed berguna untuk mengatasi masalah sangat spesifik yang dimiliki oleh pengguna (misalnya ada yang nggak suka jika app yang baru diinstall langsung memiliki shortcut di home screen, ada yang nggak suka jika VPN meminta konfirmasi, dsb). Menurut saya itu kelebihan utama XPosed: kita bisa memodifikasi Android dengan mudah sesuai kemauan kita. Sebagai programmer, modifikasi ini bisa dilakukan meskipun kita tidak memiliki source code aplikasinya.

Secara praktis bagai saya pribadi: Sebagai pentester, saya kadang menemui aplikasi yang melakukan pengecekan root dengan cara yang unik dan modul XPosed seperti RootCloak tidak cukup. Saya kadang melakukan patching aplikasi langsung (jika masih testing tahap development), dan mengembangkan frameworknya (jika aplikasi sudah di appstore, karena biasanya sudah stabil)

Kadang saya juga perlu mengintersepsi komunikasi dengan enkripsi yang custom (misalnya memakai AES dua kali), dalam kasus ini intercept via proxy tidak cukup (datanya sudah dienkrip, dan tidak bisa dengan mudah didekripsi karena formatnya custom). Di dalam kodenya pasti ada sesuatu yang mengenkrip data sebelum dikirim dan mendekrip data yang diterima, dengan mengintercept titik tersebut, saya tidak perlu menulis program khusus untuk dekrip/enkrip ketika testing.

Saya juga pernah membuat modul khusus untuk intersepsi Blackberry Messenger (BBM). Cara kerja modulnya seperti ini: saya membuat web server di Android yang bisa menerima pesan untuk dikirimkan, dan pesan itu saya inject langsung ke proses BBM. BBM selalu menyimpan message dalam format internalnya setelah diterima, jadi kita bisa mengakses message dari conversation manapun. Perlu dicatat bahwa modul ini bisa saya kembangkan dengan mudah karena di versi awal mereka tidak melakukan obfuscation, dan ketika sudah diobfuscate, saya masih bisa melihat mapping class/methodnya berdasarkan berbagai pesan error yang ada di method tersebut.

Untuk membuat XPosed module sendiri, sudah ada berbagai tutorial di Internet, tapi saya akan memberikan panduan singkat, sekaligus sebagai catatan untuk diri sendiri.

Pertama: buat proyek baru dengan Android studio. Pastikan instant run dimatikan karena tidak kompatibel dengan Xposed. Tidak perlu template khusus untuk membuat modul XPosed.

Kedua: edit file gradle dengan menambahkan dependensi: provided 'de.robv.android.xposed:api:82'. Tentunya versi 82 bisa diganti dengan versi terbaru.

Ketiga: implementasikan IXposedHookLoadPackage, ini sudah dicontohkan di atas.

Keempat: buat file app/src/main/assets/xposed_init yang adalah file teks dan isinya adalah nama package + kelas yang mengimplementasikan IXposedHookLoadPackage tersebut. Ini akan dipakai oleh Xposed untuk mendari kelas mana yang akan diload saat booting.

Terakhir: edit file manifest agar dikenali sebagai modul XPosed. Pertama tambahkan atribut xmlns:tools="http://schemas.android.com/tools" di tag manifest (ini sekedar untuk menghilangkan warning). Lalu tambahkan beberapa metadata berikut di dalam tag application:

<meta-data  android:name="xposedmodule"
android:value="true" />

<meta-data  android:name="xposedminversion"
android:value="54" />

<meta-data  android:name="xposeddescription"
android:value="Joe Hack Module"
tools:ignore="ManifestResource" />

Tentu saja bagian tersulit adalah mencari tahu bagian mana yang perlu dihook untuk mengimplementasikan fungsionalitas tertentu. Tapi menurut saya ini juga sekaligus menjadi bagian yang paling menarik: kita jadi lebih mengerti internal sistem Android.

Modifikasi dengan XPosed framework melengkapi hack Android yang lain. Rooting aplikasi tidak cukup untuk mengubah internal program (contohnya untuk pentest aplikasi custom). Modifikasi dan compile ROM sendiri bisa mengubah hal-hal yang di luar kemampuan XPosed, tapi tetap tidak bisa mengubah aplikasi. Modifikasi aplikasi dengan cara patching manual bisa dilakukan, tapi cara ini lebih repot, dan akan sulit jika kita ingin mempatch banyak aplikasi sekaligus.

Sebagai pembuat aplikasi, mengobfuscate aplikasi Anda adalah salah satu cara untuk mempersulit hooking dengan XPosed framework. Contohnya: aplikasi Youtube sering diobfuscate dan sering diupdate sehingga modul seperti YoutubeAdaway tidak jalan.