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.

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.

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.

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.

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.

Bug, Debugging, dan Debugger

Baru saja ada seseorang yang bertanya kepada saya mengenai cara mendebug program buatannya. Sebentar saya merasa heran: masak nggak bisa pake debugger? lalu ketika saya mencoba mengingat-ingat, di sebagian besar buku dan kuliah mahasiswa tidak diajarkan mengenai bug yang umum, cara mencari bug, dan cara menggunakan debugger itu sendiri.

Kemampuan mencari bug di program sendiri ini juga menjadi dasar untuk mencari bug security. Kalo kita bisa menemukan kesalahan yang kita buat sendiri, akan lebih mudah untuk mencari kesalahan di program orang lain.

Bug

Bug adalah segala macam cacat dalam program. Bisa saja cacatnya hanya berupa tampilan yang sedikit salah, bisa crash, bisa berupa bug security (harusnya hanya bisa diakses user X, bisa diakses user Y), kadang bug tertentu tidak muncul sampai kasus ekstreem (misalnya jika jumlah user banyak maka akan out of memory karena ada memory leak).

Sumber bug bisa banyak, dari mulai salah design, salah implementasi, salah konfigurasi, dsb. Sebagai programmer, bug yang akan saya bahas adalah dari sisi implementasi (coding). Biasanya seorang programmer akan belajar sedikit demi sedkit jenis bug dari pengalaman. Semakin banyak coding, semakin banyak salah, semakin banyak belajar.

Beberapa jenis bug sangat umum di semua bahasa (kesalahan logika, kesalahan aritmatika, dsb), tapi bug tertentu hanya muncul di bahasa tertentu atau jika menggunakan framework tertentu. Jadi pengalaman coding tetap merupakan guru yang utama.

Contoh bug yang sederhana adalah lupa menginisialisasi atau mereset variabel. Biasanya jika program berjalan benar pertama kali, lalu ketika diulangi tidak benar hasilnya (dan benar lagi ketika aplikasi direstart) maka penyebabnya adalah lupa mereset variabel.

Contoh lain adalah menggunakan try catch secara malas, misalnya dengan membungkus seluruh fungsi dalam satu try except. Dengan cara ini, maka tidak jelas apa sumber error di sesungguhnya.

Contoh bug yang spesifik terhadap bahasa adalah bug manajemen memori di C. Kesalahan mengakses memori di C sangat mudah terjadi dan biasanya akan menyebabkan crash.

Source Control

Jangan heran juga kalau kadang bug baru muncul karena hal sepele, misalnya source code berubah  karena keyboard tersenggol. Msalnya sebuah konstanta 1000 terdelete angka nol terakhirnya (atau bertambah satu), atau tanda “lebih besar sama dengan” terhapus  menjadi lebih besar saja. Di sini penggunakan version control (yang populer saat ini: Git) sangat berguna, Anda bisa mereview segala perubahan pada source code.

Source control juga memungkinkan kita melihat sejarah perubahan secara keseluruhan. Bagi security researcher, ini sering menjadi ladang untuk mencari bug baru di software open source. Jika ada yang menambahkan fungsionalitas baru, maka kemungkinan ada bug baru yang muncul.

Unit Testing

Mencari dan membetulkan bug itu susah (baik bug sendiri maupun orang lain), jadi hal pertama yang perlu dicoba adalah: menghindari bug. Hal yang paling dasar adalah: kita perlu memecah program kita menjadi unit yang kecil (unit di sini berupa fungsi/kelas/method) dan menguji tiap bagian kecil tersebut. Istilahnya untuk ini adalah unit testing. Kita bisa menggunakan library tertentu untuk unit testing, atau panggil saja fungsi yang akan ditest.

Saya akan mengasumsikan Anda minimal sudah bisa membuat aplikasi yang bisa dicompile dan bisa dijalankan. Jika Anda masih salah syntax, salah opsi untuk mengcompile, dsb, sebaiknya ada mulai lagi dari nol dan mulai perlahan.

Dengan memecah program menjadi unit kecil dan mengujinya satu persatu, maka kesalahan umumnya bisa dilokalisasi: fungsi X ini masih salah. Pertanyaan berikutnya adalah: apa salahnya?

Asersi

Saat ini kebanyakan bahasa memiliki fitur assert. Fitur ini memungkinkan kita melakukan pengecekan ekstra (yang biasanya tidak akan dilakukan di versi produksi). Assert ini biasanya digunakan untuk menyatakan kondisi yang pasti harus benar, karena jika tidak benar maka titik program berikutnya akan error fatal.

Ini adalah contohnya:

assert(index >= 0);

val = values[index];

Jika index kurang dari 0, maka ada sesuatu kesalahan fatal di program kita (mungkin kesalahan logika, mungkin ada hal yang lupa ditangani). Jika asersi diaktifkan biasanya program akan berhenti dengan “Assertion error”.

Di berbagai bahasa assert ini bisa diaktifkan/nonaktifkan dengan opsi tertentu. Jadi dalam versi rilis kita bisa membuang berbagai pengecekan asersi ini.

Pesan dari Compiler dan Linter

Sebelum melihat jauh ke mana-mana, coba lihat dulu apakah ada warning dari compiler Anda. Contohnya untuk potongan kode C ini:

 int i;
 printf("i = %d\n", i);

Compiler memberitahu:

warning: ‘i’ is used uninitialized in this function [-Wuninitialized]

Bacalah pesan error dan warning dengan teliti. Di situ dijelaskan bahwa variabel i tidak diinisialisasi. Di C, variabel lokal nilainya tidak bisa diprediksi jika tidak diinisialisasi.

Selain compiler, biasanya ada program yang namanya linter untuk sebuah bahasa yang bisa memberi peringatan jika ada sesuatu yang dianggap tidak standar atau aneh. Anda bisa melihat daftar lengkap software semacam ini di Wikipedia. Sebagian software ini mudah sekali diinstall, dan sebagian lagi terlalu rumit, tapi beberapa IDE sudah memiliki linter built in. Saran saya: jika Anda masih belajar dan tools linter tersedia, pakailah dan perhatikan outputnya, jika terlalu sulit mensetupnya ya sudah lupakan saja untuk saat ini.

Print debugging

Lalu berikutnya cara paling primitif dalam debugging adalah dengan menuliskan nilai saat ini menggunakan: “print” “echo”, “printf” atau sejenisnya.

Contoh sederhana, misalnya kita punya conditional di Python seperti ini (melakukan regular expression matching):

if (re.search(PATTERN, x)): # do something

Lalu bagian aksi tidak pernah dieksekusi, atau cuma dieksekusi beberapa kali. Apakah Anda yakin patternnya benar? Coba print sebelumnya, jangan-jangan regexnya masih salah untuk input tertentu:

print "Input ", x, "Match result ", re.search(PATTERN, x)

Tentunya tidak semua aplikasi bisa punya layar yang menampilkan teks itu (contohnya Game, atau aplikasi grafik lain). Biasanya cara yang lebih baik adalah menggunakan library untuk logging yang saat ini sudah built in di banyak bahasa (misalnya ada modul logger di Python, java.util.logging di Java, dsb). Output logging bisa dikonfigurasi sedetail apa, dan outputnya ke mana (ke file, ke terminal, ke jaringan, dsb).

Logging tidak hanya untuk proses development. Banyak aplikasi menyediakan fungsi logging untuk mencatat berbagai hal yang berhubungan dengan aplikasi untuk mendebug jika ada masalah ketika dijalankan. Contohnya aplikasi bisa menampilkan di lognya: “berusaha melakukan koneksi database ke server XX port YY dengan username ZZ”, yang bisa diikuti dengan “Koneksi berhasil”, atau “Koneksi Gagal, pesan error: invalid username or password”.

Debugger

Debugger adalah tools untuk mencari bug. Di kebanyakan IDE (Integrated Development Environment), tools ini sudah built in dan mudah dipakai. Ada juga debugger stand alone, tapi biasanya ini lebih sulit bagi pemula.

Untuk menggunakan debugger pertama kita perlu mengeset breakpoint, yaitu titik berhenti sementara (break). Ketika kita mengklik “debug” maka debugger akan mulai menjalankan program sampai titik itu. Di beberapa bahasa, jika kita tidak mengeset breakpoint, maka program dihentikan sementara di titik awal.

Debugger paling dasar sekalipun akan memiliki fungsi untuk menampilkan nilai variabel saat ini. Dengan menggunakan ini kita biasanya tidak perlu mencetak dengan printf, tapi untuk mencetak objek yang kompleks, kadang tetap saja keluaran print masih lebih baik.

Fungsi debugger berikutnya adalah melakukan single stepping, artinya kita bisa menelusuri langkah demi langkah program. Contohnya jika kita memiliki source sederhana

diskon = 0.1

harga = 10000

harga = harga - (diskon * harga)

Kita bisa melakukan single step dan melihat nilai diskon dan harga di setiap langkah.

contoh

Jika kita memanggil fungsi lain, maka kita memiliki dua opsi, masuk ke dalam fungsi itu (istilahnya biasanya “step into”) atau memanggil fungsi itu, lalu segera meneruskan ke baris berikutnya (kita tidak peduli atau tidak ingin mendebug fungsi itu, istilah ini “step over”).

Jika fungsi yang akan kita cek sangat jauh atau hanya dipanggil di kondisi tertentu maka kita bisa menggunakan breakpoint. Biasanya di berbagai IDE ini bisa dilakukan dengan mengklik di samping nomor baris, dan akan muncul bulatan merah. Jika kita menekan tombol “continue”, maka program akan dijalankan dan akan dihentikan sementara di titik breakpoint tersebut.

Ada juga yang namanya “conditional breakpoint”. Artinya kita akan berhenti hanya pada kondisi tertentu. Misalnya kita ingin berhenti dalam loop setelah i > 100, karena di titik itu ada data yang aneh, maka kita bisa menambahkan kondiri “i>100”

conditional

Beberapa debugger memiliki fungsi yang menakjubkan, misalnya GDB mendukung reverse execution dan Visual Studio mendukung “Edit and Continue”. Untuk perubahan kecil, kita bisa mengubah program kita tanpa menghentikan debugger.

Di beberapa environment mensetup debugging tidak mudah, tapi ketika sudah terinstall ini akan sangat berguna sekali.

Debugger level Assembly

Untuk program biasa, Anda biasanya tidak akan pernah butuh mendebug sampai level ini, tapi jika Anda membuat eksploit atau berusaha mengeksploit binary, Anda perlu tahu ini (saya pernah membuat pengantar mengenai buffer overflow jika Anda tertarik).

Debugger di level assembly sebenarnya tidak berbeda jauh dari level source code. Jika kita punya source codenya, bahkan kita bisa melihat keduanya berdampingan. Di level assembly kita bisa menelusuri tiap instruksi dan juga melihat nilai register. Dengan melihat nilai register beserta flagsnya Anda bisa belajar banyak mengenai assembly.

Tools lain

Kadang debugger saja tidak cukup, tools analisis runtime seperti strace, ltrace, valgrind dsb akan berguna membantu menemukan bug terutama di kode native. Topik mengenai tools sudah pernah saya bahas sebelumnya.

Logika

Tools apapun tidak akan bisa membantu jika kita tidak punya logika yang baik. Dasar dari debugging adalah mencari kesalahan program, lalu membetulkannya agar berjalan sesuai harapan. Jadi di tahap paling awal, Anda sendiri harus tahu harapannya apa yang terjadi di setiap langkah.

Contohnya jika Anda mendebug algoritma mencari nilai maksimum, Anda perlu mengerti dulu seperti apa seharusnya algoritmanya dan apakah ketika dijalankan sudah sesuai harapan.  Dalam kasus ini, di langkah pertama seharusnya nilai maksimum adalah elemen pertama, di langkah kedua, jika nilainya lebih besar maka elemen ini yang jadi elemen maksimum (dan jika tidak, maka elemen pertama yang tetap menjadi maksimum), dan seterusnya untuk elemen berikutnya.

Penutup

Sebenarnya bisa panjang sekali bahasan mengenai bug software, artikel ini hanya sekedar perkenalan. Anda sebaiknya mendalami berbagai tools supaya lebih produktif dalam bekerja, dari mulai tools source control, editor, debugger, linter, build system (Makefile, CMake, Ant, Maven, atau apapun), maupun tools lainnya.

Untuk Anda yang baru belajar memprogram: jika Anda menggunakan IDE, sesekali tekanlah tombol debug (atau menu debug) dan cobalah bereksperimen menggunakan fungsi dasar seperti step into, step over, dsb. Ini akan sangat membantu Anda dalam belajar pemrograman.

 

 

Membuat Bot Telegram

Messenger telegram masih kurang populer di Indonesia, padahal Telegram memiliki banyak kelebihan dibanding messenger lain, misalnya: lebih cepat, bisa diakses via web, sinkronisasi pesan, dsb. Salah satu fitur yang baru diperkenalkan oleh Telegram adalah bot. Bot adalah software yang secara otomatis merespon pesan yang kita berikan.

Setelah saya membuat bot Bot Alkitab, beberapa orang bertanya bagaimana caranya membuat bot. Dengan harapan Telegram jadi lebih populer, saya tuliskan di sini dasar pembuatan bot.

Sebelum membaca Artikel ini, silakan baca dulu artikel dari telegram yang memperkenalkan soal bot, lalu bereksperimen lah dengan bot-bot yang sudah ada, supaya bisa mengerti fitur bot dan interaksi dengan bot:

https://telegram.org/blog/bot-revolution

Mendaftarkan Bot

Sekarang setelah siap membuat bot, langkah pertama adalah mendaftarkan nama bot kita. Ini dilakukan dengan menggunakan BotFather.

Perintah yang perlu digunakan adalah /newbot. Perlu diperhatikan bahwa semua bot harus memiliki akhiran “Bot”. Setelah selesai, kita akan mendapatkan token, semacam ini: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11. Token ini tidak boleh disebar (karena orang lain bisa mengambil alih bot Anda).

botfather

Setelah didaftarkan, kita bisa langsung menambahkan bot di daftar teman kita.

daftar-teman

Ketika mulai chat, ada tombol “Start”

tombol-start

Jika kita tekan itu, yang terjadi adalah kita mengirimkan perintah “/start” ke bot (semua perintah bot dimulai dengan “/”).

pesan-pertama

Tapi kita belum menulis kode untuk bot kita, jadi bot kita belum bisa membalas apapun.

Menerima Pesan

Bot bisa menerima pesan dengan dua cara: polling dan webhook.

Polling artinya kita yang akan meminta pesan ke server telegram. Kira-kira begini: eh ada nggak pesan buat bot saya, kalo nggak ada, bilang aja nggak ada setelah N detik ya. Kalau dalam N detik ada pesan masuk, langsung berikan ke saya pesannya.

Cara kedua adalah dengan webhook. Kira-kira begini: ini saya punya URL, kalau ada pesan masuk, segera posting ke URL ini ya, nanti saya proses. URL ini harus HTTPS.

Telegram hanya mau mengantarkan pesan dengan salah satu cara di atas, tidak bisa keduanya, defaultnya adalah cara pertama.

Cara pertama cocok jika server dijalankan secara internal. Misalnya jika dijalankan di raspberry pi di rumah. Kita tidak perlu IP publik, tidak perlu punya domain, tidak perlu setup HTTPS, dsb. Cara ini agak sedikit lebih lambat dari cara kedua jika penggunanya banyak.

Cara kedua cocok jika Anda sudah punya web server eksternal dan sudah punya HTTPS, atau memakai layanan yang sudah memakai HTTPS (contohnya Google App Engine).

Pertama saya bahas cara polling karena ini sangat praktis ketika development. Pertama kita coba akses pesan yang diterima bot. Kita bisa meminta menggunakan browser seperti ini:


https://api.telegram.org/botTOKEN/getUpdates

Contohnya seperti ini:

getupdates

Sebenarnya ada beberapa parameter ekstra yang bisa kita tambahkan. Pertama adalah timeout: menyatakan berapa lama kita akan menunggu sampai ada pesan. Kedua adalah limit yang menyatakan berapa pesan yang ingin kita terima (max 100). Dan ketiga yang penting adalah offset.

Jika Anda coba refresh URL sebelumnya (tanpa memberikan offset), maka telegram tidak tahu apakah Anda sudah memproses pesan itu atau tidak (batasnya 24 jam). Untuk memberi tahu telegram bahwa pesan tadi sudah kita baca, kita perlu memberikan offset berupa update_id terakhir plus 1, jadi untuk mendapatkan pesan-pesan setelah pesan di atas, saya bisa menambahkan offset seperti ini (sekalian saya berikan contoh memberikan timeout dan limit):


https://api.telegram.org/botTOKEN/getUpdates?offset=142757037&timeout=2&limit=1

Hasil kembalian getUpdates seperti ini:

{
 "ok":true,
 "result":[
 {
 "update_id":142757036,
 "message":{
 "message_id":3,
 "from":{
 "id":24912310,
 "first_name":"Yohanes",
 "last_name":"Nugroho",
 "username":"yohanesn"
 },
 "chat":{
 "id":24912310,
 "first_name":"Yohanes",
 "last_name":"Nugroho",
 "username":"yohanesn"
 },
 "date":1436950746,
 "text":"\/start"
 }
 }
 ]
}

Isi “result” adalah array of message (ada berbagai jenis message, bisa teks, video, dokumen, dsb, tapi dalam tutorial ini yang akan saya tangani hanya yang berisi “text”. Anda bisa membaca API Telegram untuk menangani jenis pesan yang lain.

Untuk membalas pesan, kita bisa menggunakan sendMessage. Anda bisa mencoba di browser (menggunakan token Anda) seperti ini (lihat chat_id di JSON di atas):


https://api.telegram.org/botTOKEN/sendMessage?chat_id=24912310&text=hello

Jika kita ingin menampilkan pesan sebagai balasan dari message tertentu, kita bisa menambahkan reply_to_message_id. Misalnya di atas tercantum message_id adalah 3:


https://api.telegram.org/botTOKEN/sendMessage?chat_id=24912310&text=hello&message_id=3

Jika kita menggunakan Python, kita bisa mendapatkan pesan updatenya dengan library requests (urllib juga bisa, tapi requests lebih enak). Karena URL requestnya sama semua prefixnya, saya membuat fungsi request_url untuk mempersingkat. Berikutnya agar singkat, saya hanya akan membahas PHP saja (dan Anda tetap bisa dengan mudah menerjemahkan ke Python).

def request_url(method):
    return  "https://api.telegram.org/bot"+TOKEN+"/" + method

def getUpdates(offset, limit=100, timeout=100):
    r = requests.get(request_url("getUpdates"),
                     params={"offset":offset,
                      "limit": limit,
                      "timeout": timeout
                      } )
    #asumsi semuanya OK
    result = r.json()
    if result["ok"]:
        return result["result"]
    return []

Kode yang sama di PHP:

<?php

include("token.php");

function request_url($method)
{
	global $TOKEN;
	return "https://api.telegram.org/bot" . $TOKEN . "/". $method;
}

function get_updates() 
{
        $resp = file_get_contents(request_url("getUpdates"));
        $update = json_decode($resp, true);
         
?>

Kerangka sebuah program PHP command line (metode polling) yang menangani pesan adalah seperti ini:

<?php
include("token.php");

function request_url($method)
{
	global $TOKEN;
	return "https://api.telegram.org/bot" . $TOKEN . "/". $method;
}

function get_updates($offset) 
{
	$url = request_url("getUpdates")."?offset=".$offset;
        $resp = file_get_contents($url);
        $result = json_decode($resp, true);
        if ($result["ok"]==1)
            return $result["result"];
        return array();
}

function send_reply($chatid, $msgid, $text)
{
    $data = array(
        'chat_id' => $chatid,
        'text'  => $text,
        'reply_to_message_id' => $msgid

    );
    // use key 'http' even if you send the request to https://...
    $options = array(
    	'http' => array(
        	'header'  => "Content-type: application/x-www-form-urlencoded\r\n",
        	'method'  => 'POST',
        	'content' => http_build_query($data),
    	),
    );
    $context  = stream_context_create($options);

    $result = file_get_contents(request_url('sendMessage'), false, $context);
    print_r($result);
}

function create_response($text)
{
   return "definisi " . $text;
}


function process_message($message)
{
    $updateid = $message["update_id"];
    $message_data = $message["message"];
    if (isset($message_data["text"])) {
	$chatid = $message_data["chat"]["id"];
        $message_id = $message_data["message_id"];
        $text = $message_data["text"];
        $response = create_response($text);
        send_reply($chatid, $message_id, $response);
    }
    return $updateid;
}


function process_one()
{
	$update_id  = 0;

	if (file_exists("last_update_id")) {
		$update_id = (int)file_get_contents("last_update_id");
	}

	$updates = get_updates($update_id);

	foreach ($updates as $message)
	{
     		$update_id = process_message($message);
	}
	file_put_contents("last_update_id", $update_id + 1);

}

while (true) {
	process_one();
}
          
?>

Anda bisa mengakses sourcenya di sini:

https://gist.github.com/yohanes/987e1d48cbca9fde9377

Perhatikan bahwa ini perlu dijalankan di command line seperit ini:

$ php telegram-poll.php

Dan selama program masih berjalan, maka pesan yang masuk akan dibalas. Jika dijalankan di web server, maka akan timeout setelah beberapa detik/menit.

Isi file token.php hanyalah seperti ini:

<?php
$TOKEN="TOKENANDA";
?>;

Untuk metode webHook, perbedaannya hanya pada fungsi get_updates, dan process_one tidak diperlukan, loop while juga tidak diperlukan, karena pesan akan dikirim satu per satu dengan POST. Kita cukup membaca POST yang masuk (dengan membaca php//:input), dan mendecode JSON-nya.

<?php
//bagian awal sama dengan sebelumnya

$entityBody = file_get_contents('php://input');
$message = json_decode($entityBody, true);
process_message($message);

?>

Source lengkap di:

https://gist.github.com/yohanes/3181a5424d10ea5e2429

Untuk menggunakan methode webHook, Anda harus melakukan beberapa hal:

  1. Mempersiapkan URL HTTPS
  2. Menset urlWebhook

Untuk bagian pertama: Anda perlu memiliki hosting yang mendukung HTTPS, Anda juga bisa memakai layanan cloudflare.com yang otomatis membungkus web Anda dengan https. Berikutya kita perlu mensetup web hook. Contohnya jika URL file php tersebut adalah:

http://cintaprogramming.com/xx.php

maka kita set dengan cara mengunjungi (menggunakan browser atau curl):

https://api.telegram.org/botTOKEN/setWebhook?url=https://cintaprogramming.com/xxx.php

Anda akan mendapatkan balasan:

{"ok":true,"result":true,"description":"Webhook was set"}

Jika ingin kembali menggunakan metode polling, maka set URL menjadi kosong:

https://api.telegram.org/botTOKEN/setWebhook?url=

Anda akan mendapatkan balasan:

{"ok":true,"result":true,"description":"Webhook was deleted"}

Atau jika memang belum diset:

{"ok":true,"result":true,"description":"There is no webhook to delete"}

Berikutnya terserah Anda bagaimana ingin memproses data masukan dari pengguna, misalnya bisa query ke database, bisa melakukan komputasi, dsb.

Jika kode Anda jalan di versi lokal/polling, tapi tidak ketika menggunakan webhook, maka Anda perlu mendebug. Pertama test apakah: webhook dipanggil oleh telegram (bisa dicoba dengan menuliskan isi $entityBody ke file). Kedua cek apakah Anda bisa memanggil send_reply. Beberapa hosting mematikan file_get_contents untuk mengakses HTTP/HTTPS, atau mendisable modul SSL (untuk kasus ini, mungkin harus diganti memakai modul curl) . Coba skrip paling sederhana di atas sebelum menambahkan kode custom Anda untuk reply (misalnya Anda mengakses MySQL, siapa tahu kode Anda yang error karena salah database atau salah query).

Saran saya: gunakan polling dulu sampai yakin sudah benar, setelah itu baru gunakan webhook.

Contoh ini sebenarnya adalah penyederhanaan sebuah kamus. Seharusnya bot ini membalas dengan definisi dari database, tapi di contoh ini bot selalu membalas dengan “definisi ” + KATA.

Sebuah bot bisa memiliki banyak perintah yang diawali dengan “/” (slash), terserah pembuat bot yang mendefinisikan. Misalnya jika kita membuat bot cuaca, kita bisa membuat perintah: “/cuaca NAMAKOTA” (untuk mendapatkan cuaca saat ini) atau “/ramalan NAMAKOTA” (untuk mendapatkan ramalan seminggu). Untuk menangani perintah-perintah ini kita bisa menggunakan parsing teks sederhana. Contoh parsing yang bisa dilakukan split teks menjadi kata-kata: jadikan kata pertama menjadi lower case, dan trim (hapus spasinya), lalu cek apakah sama dengan “/cuaca” atau “/ramalan”. Dan berikutnya lakukan aksi sesuai dengan perintahnya.

Setelah kita memiliki perintah-perintah tersebut. Kita bisa mendaftarkan perintahnya ke BotFather supaya Telegram bisa melakukan autocomplete perintah ketika user mengetikkan karakter /.

Demikian tutorial singkat bot Telegram. Jika ada pertanyaan, silakan tinggalkan di bagiann komentar, dan akan berusaha saya jawab (atau mungkin akan saya buatkan tutorial bagian kedua untuk menjelaskan lebih detail).

Sencha Touch di PlayBook

Ini catatan teknis mempackage aplikasi Sencha Touch menjadi file .bar PlayBook. Pertama download dulu Sencha Touch 2, dan Juga SDK Toolsnya.

Saya memakai Mac, tapi tutorial ini berlaku juga untuk OS Windows. Perhatikan bahwa Anda perlu punya web server terinstall. Di Mac, Apache sudah ada default. Di Windows, silakan install IIS atau Apache (misalnya dari Wamp).

Extract file sencha-touch-2.0.1.1-commercial.zip. Saya mengekstraknya di dalam ~/Sites/ , lalu saya rename jadi “sencha” saja, jadi file-file sencha-touch-all.js, sencha-touch.js dsb ada di ~/Sites/sencha/.

Berikutnya kita buat aplikasi hello world. Sekarang dari dalam ~/Sites/sencha kita bisa menjalankan perintah “sencha generate app” untuk membuat aplikasi:

$ cd ~/Sites/sencha/
$ sencha generate app --name=HelloWorld -p ~/Sites/helloworld

Aplikasinya bisa dites dengan mengakses di http://localhost/~yohanes/helloworld (atau tergantung setting web server Anda bagaimana).

Saya tidak akan mengubah file-filenya, jadi saya langsung package saja. Pertama buat dulu file config.xml. Saya memakai file template yang sudah biasa saya pakai. Sebenarnya baris-baris feature itu tidak diperlukan untuk ini, tapi karena saya sering lupa menambahkan baris tersebut ketika memakai API Blackberry, saya selalu menggunakan template ini.

Catatan: saya tidak menggunakan ikon untuk aplikasi ini (blank). Silakan ditambahkan sendiri jika dibutuhkan.

<?xml version="1.0" encoding="UTF-8"?>

<widget xmlns="http://www.w3.org/ns/widgets"
   xmlns:rim="http://www.blackberry.com/ns/widgets"
   version="1.0.0.0" id="helloworld" xml:lang="en">

   <name>Sencha Hello</name>
   <description>Sencha Hello.</description>
   <author rim:copyright="2012" email="[email protected]">Yohanes Nugroho</author>

   <rim:orientation mode="landscape" />

   <content src="index.html"/>
   <rim:category name="Games"/>

   <feature id="blackberry.app" required="true" version="1.0.0.0"/>
   <feature id="blackberry.app.event" required="true" version="1.0.0.0"/>
   <feature id="blackberry.system" required="true" version="1.0.0.0"/>
   <feature id="blackberry.system.event" required="true" version="1.0.0.0"/>
   <feature id="blackberry.ui.dialog" required="true" version="1.0.0.0"/>
   <feature id="blackberry.utils" required="true" version="1.0.0.0"/>
   <feature id="blackberry.invoke" required="true" version="1.0.0.0"/>
   <access subdomains="true" uri="*">
   </access>
</widget>

Sekarang kita bisa men-zip aplikasinya. Masalah utama adalah: tools webworks tidak suka dengan file yang memiliki nama dengan tilde (~) dan dengan at(@) (misalnya [email protected], ini untuk retina display). Ada beberapa file seperti ini, untungnya ini tidak diperlukan, jadi kita bisa kecualikan dari file zip

zip -r senchahello.zip config.xml app.js app.json index.html resources/ sdk/ app/ -x *2x* -x *~*

Kuncinya ada pada “-x *2x* -x *~*” untuk meng-exclude file yang tidak valid menurut webworks.

Berikutnya kita bisa mempaketkan file zip tersebut menjadi file bar. Untuk mempersingkat command line, saya menginstall SDK di home directory saya di direktori webworks-tablet.

~/webworks-tablet/bbwp/bbwp senchahello.zip -g password -v buildId 1

Catatan: jika versi tidak diubah, buildId perlu dinaikkan setiap kali build.

Sekarang file .bar akan tercipta dalam direktori ~/Sites/helloworld/bin siap untuk diinstall ke PlayBook.

Catatan Teknis – Baby Coloring Book

Setiap kali saya membuat aplikasi dengan teknologi yang baru, biasanya saya mendapati banyak tantangan teknis. Dalam konteks ini “baru” bisa berarti teknologinya benar-benar baru, atau saya yang baru saja mengenal teknologi tersebut. Kali ini saya ingin membahas mengenai aplikasi saya di appworld, Baby Coloring Book.

Ide dari aplikasi ini sangat sederhana: buku mewarnai untuk bayi (terutama di bawah 3 tahun), hanya perlu menyentuh saja untuk mewarnai, tidak perlu menggosok-gosok seperti memakai krayon. Jika saya menggunakan teknologi lain untuk membuat ini (misalnya flash atau C++), saya akan menggunakan pendekatan sederhana: buat gambar tidak berwarna, gunakan flood fill untuk mengisi area. Tapi saya menggunakan HTML5 untuk membuat aplikasi ini. Alasan utamanya adalah untuk belajar mengenal lebih jauh HTML5. Saya tidak menggunakan library selain selain JQuery.

Contoh Gambar

Di Stack Overflow sudah ada yang menjawab bagaimana mengimplementasikan flood fill dengan JavaScript menggunakan Canvas:

How can I perform flood fill with HTML Canvas?

Jadi saya coba itu di PC menggunakan browser Google Chrome. Manipulasi piksel di Google Chrome sangat cepat, tapi saya melihat ada sedikit delay. Saya langsung curiga: jangan-jangan jika saya coba di PlayBook akan sangat lambat. Ternyata benar: sangat lambat.

Ada beberapa pendekatan yang terpikir oleh saya supaya aplikasi ini bisa dibuat dengan HTML5 tapi tetap cepat, tapi saya memiliki beberapa requirement:

  1. Saya tidak ingin “membatik” mendefinisikan setiap area gambar yang bisa diwarnai. Jika saya punya gambar, saya ingin langsung bisa memakai gambar itu tanpa edit manual. Dengan pendekatan flood fill, algoritma tersebut bisa otomatis mewarnai sebuah area yang dibatasi pixel tertentu (seperti mewarnai dengan “ember” di Ms Paint).
  2. Saya ingin bisa membuat gambar yang warnanya seperti diwarnai dengan krayon, jadi tidak polos.

Continue reading Catatan Teknis – Baby Coloring Book

Pentingnya memahami Ilmu Informatika secara menyeluruh

Hari ini saya menemukan link ke sebuah pertanyaan menarik di Stack Overflow. Sebuah pertanyaan sederhana: mengapa menjumlahkan elemen yang nilainya kurang dari nilai tertentu dalam array yang terurut, lebih cepat dari melakukan operasi yang sama pada array yang tidak terurut. Lebih jelasnya silakan baca pertanyaan dan jawabannya di sini:

Why is processing a sorted array faster than an unsorted array?

Ada beberapa hal menarik dari jawaban pertanyaan tersebut.

Pertama: meskipun Anda memprogram high level sekalipun (menggunakan Java/Ruby/Python, atau bahasa lain yang menggunakan JIT), Anda akan tetap dibatasi oleh hardware. Anda tetap perlu mengerti hardware untuk membuat aplikasi yang performasinya tinggi.

Kedua: perhatikan bahwa dengan mengetahui sebab dari masalah, kita bisa mempercepat program, tanpa menggunakan sorting. Cukup dengan menggunakan manipulasi bit yang menghilangkan branching. Kita ingin menghilangkan sorting, karena sorting sendiri butuh waktu.

Ketiga: dalam kasus tertentu, compiler bisa mengoptimasi jika diberi flag yang tepat. Tapi kita tidak bisa menggantungkan diri pada compiler saja. Compiler yang berbeda menghasilkan kode yang berbeda, dan hasilnya bisa sangat jauh berbeda. Misalnya disebutkan bahwa compiler Visual C++ 2010 tidak bisa mengoptimasi kodenya, sedangkan compiler Intel bisa melakukannya dengan sangat baik.

Keempat: Perhatikan juga bahwa optimasi compiler dibatasi oleh hardware. Hardware tertentu (misalnya Intel sejak Pentium Pro) mendukung instruksi conditional move (di assembly Intel, instruksi ini disebut dengan CMOV) yang tadinya perlu manipulasi bit manual (AND, OR, dsb). Anda tidak bisa menggunakan optimasi ini di semua hardware, apalagi jika Anda menargetkan CPU model lama (banyak digunakan di embedded device).

Mungkin sebagian dari Anda mengira pertanyaan tersebut agak mengada-ada: untuk apa mencari jumlah bilangan yang kurang dari N dan dilakukan berulang-ulang, kalau sekali saja kan hanya butuh beberapa milidetik. Dan berbeda beberapa milidetik saja kan harusnya tidak berpengaruh bagi user.

Saya terpikir beberapa aplikasi dalam dunia nyata yang mungkin membutuhkan penjumlahan secara cepat tapi berulang-ulang. Saya berikan contoh kecil: Misalnya Anda punya aplikasi interactive data viewer, dengan slider yang bisa diubah nilainya dengan mouse (sangat cepat)

  1. Anda punya array yang berisi daftar jumlah gaji semua orang pegawai di sebuah kota (arraynya tidak terurut)
  2. Kita ingin menampilkan secara interaktif: jika saya set slider ke nilai 1 juta, maka saya akan melihat bahwa total gaji semua orang yang dibawah satu juta adalah X
  3. saya bisa mengubah nilai di slider, dan menghitung ulang total semua orang yang gajinya di bawah 2 juta. Saya bisa menaik turunkan slider dengan sangat cepat, ratusan kali per detik nilai slider bisa berubah.

Perhatikan bahwa meskipun contoh ini hanya menyatakan kurang dari X, tapi sebenarnya berlaku juga untuk operasi lebih dari X, atau X dalam range tertentu.

Dalam contoh ini: perbedaan interaksi antara beberapa milidetik dan beberapa puluh milidetik bisa sangat terasa. Jadi mengerti untuk mengurutkan data (atau menggunakan trik manipulasi bit) sebelum menjumlahkan bisa membuat interaksi semakin smooth. Menggunakan database untuk tujuan animasi yang sangat smooth seperti itu tidak akan berhasil (latensinya sangat tinggi), apalagi misalnya devicenya kemampuannya processing/komputasinya rendah (misalnya tablet atau smartphone).

Sebenarnya saya bisa menunjukkan contoh yang lebih kompleks lagi (misalnya dalam hal komputasi piksel grafik), tapi nanti pembahasannya akan ngelantur ke mana-mana. Hal yang ingin saya tekankan adalah: ada banyak persoalan serupa dalam dunia nyata semacam ini. Ini adalah penyederhanaan, supaya inti masalah bisa dilihat lebih jelas.

Seringkali jika ada yang menunjukkan bahwa optimasi seperti ini diperlukan, jawaban programmer yang malas adalah: beli saja hardware yang lebih cepat, masalahnya kan beres. Perlu dicatat juga: bahwa membeli hardware yang lebih cepat tidak selalu menjadi solusi.

Misalnya Anda menjual aplikasi Anda di Apple appstore, Anda harus mendukung hardware terlambat sampai tercepat. Jika Anda bandingkan iPad generasi pertama dan kedua, maka perbedaan hardwarenya sangat jauh: memori menjadi 2x lipat, prosessor menjadi jauh lebih cepat (dari single menjadi dual core). Anda bisa mengabaikan 14.8 juta pengguna iPad 1, tapi penjualan aplikasi Anda bisa menurun jauh.

Mungkin Anda terpikir untuk melakukan komputasi di server saja. Tapi berapa delay karena latensi jaringan? apakah kecepatannya cukup acceptable untuk membuat interaksi yang smooth?

Jika saya rangkum, semua hal tersebut menunjukkan: betapa perlunya kita belajar ilmu informatika atau computer science secara baik dan menyeluruh. Misalnya dalam contoh yang sangat kecil ini:

  1. Dalam arsitektur komputer, kita belajar mengenai batasan hardware, bagaimana arsitektur CPU superscalar bekerja.
  2. Dalam pelajaran algoritma, kita belajar mengenai kompleksitas algoritma. Bagaimana memilih algoritma yang baik.
  3. Dalam pelajaran compiler, kita bisa tahu optimasi apa yang bisa (dan tidak bisa) dilakukan oleh compiler
  4. Dalam pelajaran networking, kita bisa tahu mengenai latensi jaringan (jika ingin memindahkan komputasi ke server)

Jadi menurut saya, orang-orang yang ingin membatasi pelajaran komputer hanya dengan materi yang praktis saja, tidak akan berhasil.