BAB 2
DASAR TEORI
Metode untuk meningkatkan obscurity disebut obfuscation. Menurut Collberg[6] et al. Obfuscation ialah suatu proses transformasi yang dilakukan pada sebuah obyek obyek di dalam suatu program (class, method, field, dan bagian lain dari program) untuk menghasilkan program lain yang memiliki karakteristik berikut:
1. memiliki perilaku yang sama dengan program asli (semantic-preserving) 2. lebih sulit dipahami atau di-reverse engineer
3. sulit untuk membuat tool yang dapat mentransformasikan balik program tersebut ke bentuk aslinya (atau tool semacam itu akan sangat lama dalam proses pengerjaannya)
4. meminimalkan pertambahan ruang dan waktu yang dibutuhkan dalam eksekusinya (dibandingkan eksekusi program asli)
Untuk mengukur kualitas dari sebuah transformasi obfuscation, Collberg dan Thomborson[6] memberikan tiga jenis ukuran yang penting:
1. Potensi, adalah seberapa jauh obfuscation itu meningkatkan kompleksitas program
2. Resiliensi, adalah seberapa sulit untuk melakukan transformasi balik menggunakan program otomatis
3. Cost, adalah berapa execution time dan memory space tambahan yang dibutuhkan setelah program ditransformasi
Dalam kaitannya dengan ukuran kompleksitas pada (1), Collberg[7] et al. juga memberikan ringkasan dari metrik-metrik yang umum dipakai dalam mengukur kompleksitas program:
m1: Panjang program, dihitung dari banyaknya operator dan operand dalam program. Ukuran ini diusulkan oleh Halstead.
m2: Kompleksitas siklomatis (Cyclomatic complexity), dihitung dari banyaknya predikat dalam program. Ukuran ini diusulkan oleh McCabe.
m3: Nesting complexity, dihitung dari berapa tingkat persarangan terbesar yang ada di ekspresi kondisional dalam program. Ukuran ini diusulkan oleh Harrison. m4: Kompleksitas aliran data, dihitung dari banyaknya referensi variabel
antarblok dalam program. Ukuran ini diusulkan oleh Oviedo.
m5: Kompleksitas struktur data, dihitung dari kompleksitas struktur data statis dalam program. Skalar memiliki kompleksitas 1, array memiliki kompleksitas berupa hasilkali antara dimensi array dengan kompleksitas elemen array, kompleksitas sebuah record diukur dari banyaknya field dan kompleksitasnya masing-masing. Ukuran ini diusulkan oleh Munson.
2.1 Klasifikasi Obfuscating Transformations
Obfuscation dilakukan dengan menerapkan transformasi pada program sumber,
menghasilkan program yang terobfuscate. Dalam Taxonomy Of Obfuscating
Transformations, Collberg et al.[7] membagi transformasi untuk obfuscation menjadi empat kelompok besar
A. Layout Obfuscation, obfuscation jenis ini dilakukan dengan menghilangkan informasi seperti komentar, nomor baris, dan merubah nama-nama identifier dalam program.
B. Data Obfuscation, obfuscation jenis ini merubah representasi data di dalam memori dan mentransformasi semua pengaksesan data yang bersangkutan sehingga semantik asli penggunaan data tidak berubah.
C. Control Obfuscation, merubah control-flow dengan memindahkan letak blok-blok bytecode, merubah urutan instruksi, menambahkan predikat tersamar (opaque predicates[8] ). Control obfuscation dilakukan dengan transformasi yang merubah agregasi, urutan, ataupun komputasi aliran kendali.
D. Preventive Transformation, termasuk di sini ialah transformasi yang bertujuan menjebak decompiler sehingga gagal dalam tugasnya. Jenis ini dibagi lagi menjadi targeted transformation (menjebak decompiler tertentu)
dan inherent transformation (memanfaatkan masalah yang umum dalam dekompilasi).
Pada prakteknya, sebuah obfuscator akan menerapkan kombinasi dari macam-macam metode transformasi untuk menghasilkan program yang ter-obfuscate dengan baik.
Obfuscating Transformation (berdasarkan target transformasi)
Layout obfuscation
Data Obfuscation Control Obfuscation
Transformasi Preventif
Targeted Inheren
Agregasi Urutan Komputasi Penyimpanan dan
pengkodean
Agregasi
Urutan
Gb. 2.1 Taksonomi obfuscation menurut Collberg, Thomborson, dan Low [7] .
2.2 Daftar Obfuscating Transformations
Daftar ini berisi transformasi yang ditemukan dalam obfuscator-obfuscator yang menjadi subyek perbandingan
2.2.1 Pengacakan nama
Transformasi pertama ialah name obfuscation atau pengacakan nama. Collberg et al memasukkan transformasi ini ke ketegori Layout Obfuscation. Obfuscator generasi pertama hanya melakukan obfuscation jenis ini, termasuk ProGuard dan yGuard.
Tabel 2.1 Contoh name obfuscation
AlienEntity collidedWith void ... (Entity)
-> P A void ...
(Entity)
AlienEntity Move void ...
(long) -> P A void ... (long) AlienEntity setHorizontalM ovement void ... (double) -> P A void ... (double)
AlienEntity doLogic void ... () -> P A void ... ()
AlienEntity Draw void ... () -> P B void ... ()
AlienEntity getX int ... () -> P A int ... ()
AlienEntity getY int ... () -> P B int ... ()
Tiga kolom dari kiri menunjukkan nama asli, tiga kolom dari kanan menunjukkan nama setelah dilakukan name obfuscation. Empat entry terakhir menunjukkan eksploitasi spesifikasi penamaan di JVM yang lebih leluasa daripada spesifikasi penamaan bahasa pemrograman Java, yaitu bahwa di Java tidak dibolehkan ada dua fungsi yang hanya berbeda pada return value-nya. Hal ini dilarang oleh Java
Language Specification (JLS 3.0 [11] , butir 8.4.2) :
It is a compile-time error to declare two methods with override-equivalent signatures (defined below) in a class.
Two methods have the same signature if they have the same name and argument types.
Meskipun demikian, hal ini bisa diterima oleh JVM asalkan method descriptornya berbeda (Java VM Specification 2.0[15] , subbab 4.6) :
4.6 Methods
Each method, including each instance initialization method (§3.9) and the class or interface initialization method (§3.9), is described by a method_info structure. No two methods in one class file may have the same name and descriptor (§4.3.3).
Butir 4.3.3 :
A method descriptor represents the parameters that the method takes and the value that it returns:
MethodDescriptor:
( ParameterDescriptor* ) ReturnDescriptor
Kesimpulannya ialah JVM memperhatikan parameter dan return type, dan bahasa Java hanya memperhatikan parameter. Hal serupa terjadi pada fields, pada sebuah
kelas yang sama JVM membedakan field yang namanya sama dan berbeda tipenya,
sedangkan Java hanya memperbolehkan field yang namanya berbeda. Resiliensi dari transformasi jenis ini tinggi, karena dibutuhkan pengerjaan manual untuk membalikkan nama-nama yang sudah dirubah.
2.2.2 Enkripsi String
String yang ada di dalam kode merupakan titik awal reverse engineering yang umum (misalnya, melakukan pencarian string ’expired’ untuk memperkirakan klas mana yang mengandung rutin pembatas waktu). Dengan meng-encrypt string ini, reverse
engineering akan menjadi lebih sulit. Semua obfuscator komersial menerapkan obfuscation jenis ini. Transformasi ini masuk ke kategori Data Obfuscation.
Ditemukan dua variasi untuk enkripsi string yang dilakukan :
a. melakukan decryption pada saat kelas diinisialisasi (Zelix KlassMaster, SmokeScreen)
b. melakukan decryption pada saat string diakses (DashO)
Pembalikan enkripsi bisa dilakukan. Untuk kasus DashO, bisa dibuat transformasi
bytecode untuk mengganti string terenkripsi dengan yang sudah di-decrypt, dan
menghilangkan instruksi pemanggilan rutin decrypt di bytecode, menghasilkan
bytecode yang sama dengan sebelum terjadinya string encryption. Proof-of-concept
telah dibuat dan listingnya ada di lampiran. Untuk kasus Zelix KlassMaster dan
dapat dilihat di lampiran. Karena transformasi balik dapat dibuat, maka disimpulkan bahwa transformasi ini memiliki resiliensi rendah. Hasil dekompilasi Dava pada enkripsi string ZKM disajikan pada Gambar 2.2.
Gb. 2.2 Program asli dan setelah string encryption oleh ZKM package strs;
import java.util.Date; import java.io.PrintStream; public class Main1
{
static String astr;
private static final String z; static
{
long l3;
int i1, $i4, $i5; char[] $r3, $r4, $r5; char $c6, $c8; $r3 = "R>X@CD2FF\fV6UKCP2ZB\u0002ZwGD\u0017AwGQ \u0011]9S".toCharArray(); $r4 = $r3;
for ($i4 = $r3.length , i1 = 0; $i4 > i1; i1++)
{ $r5 = $r4; $i5 = i1; $c6 = $r4[i1]; label_0: switch (i1 % 5) { case 0: $c8 = '4'; break label_0; case 1: $c8 = 'W'; break label_0; case 2: $c8 = '4'; break label_0; case 3: $c8 = '%'; break label_0; default: $c8 = 'c'; break label_0; } $r5[$i5] = (char) ($c6 ^ $c8); } z = (new String($r4)).intern(); l3 = 10L + (new Date()).getTime(); astr = Long.toString(l3); }
public static void main(String[] r0) {
System.out.println(z); }
}
package strs;
public class Main1 { public static void
main(String[] s)
{
System.out.println("f ile percobaan dengan satu string");
}
static String astr; static { long a=10; a = a + new java.util.Date().getTime(); astr = Long.toString(a); } }
2.2.3 Transformasi ekspresi aritmetik
Transformasi ini dilakukan pada ekspresi berjenis integer (bilangan bulat) untuk mengganti konstanta dengan ekspresi yang lebih rumit. Transformasi ini hanya dilakukan oleh JBCO dengan nama CAE2BO [1] , meskipun pada implementasi yang sekarang tidak cukup handal untuk digunakan. Ide dari transformasi ini sama dengan transformasi Add Rendundant Operands yang ditulis oleh Collberg et al, yaitu menggunakan hukum aljabar untuk menambahkan operand yang sama ke dalam ekspresi.
Contoh: x = v*195
Karena 195 = 27 + 26 + 21 + 1, ekspresi ini dapat dirubah menjadi x = (v<<7) + (v<<6) + (v<<1) + v
dan dirubah lagi menjadi (dengan memanfaatkan fakta bahwa JVM hanya memperhatikan 5 bit terendah dari operand banyaknya pergeseran bit)
x = (v << 39) + (v << 38) + (v << 33) + v
Implementasi JBCO memiliki kelemahan dalam rutin pengubah pembagian, yang mengakibatkan ia melakukan operasi bit shift meskipun pembaginya bukan eksponen dari 2.
2.2.4 Menyimpan variabel lokal dalam bitfield
Transformasi ini mengumpulkan variabel-variabel lokal menjadi variabel besar berukuran 64-bit (tipe long). Dalam sebuah variabel bertipe long bisa disimpan dua buah variabel 32-bit (tipe int) atau 64 buah variabel boolean (karena tiap boolean hanya membutuhkan 1 bit). Transformasi ini masuk ke dalam ketegori Data
Obfuscation, dan serupa dengan ’Merge Scalar Variable’ yang dibahas oleh Collberg
et al[6] . Transformasi ini memiliki keuntungan bahwa ia dapat mengurangi kebutuhan memori untuk penyimpanan data, meskipun ia meningkatkan banyaknya instruksi dalam program. Hanya dilakukan oleh JBCO.
2.2.5 Pengubahan urutan blok statement
Transformasi ini memindahkan blok blok statement menggunakan instruksi goto untuk merubah urutan blok statement dalam bytecode. Esensi Control Flow Graph tidak diubah, tetapi pada beberapa kasus decompiler akan gagal merestrukturisasi
source Java. Decompiler Jad, misalnya, akan gagal merestrukturisasi switch yang
sudah diubah urutannya, sedangkan decompiler Dava tetap berfungsi baik. Di sisi lain, Dava mengalami kesulitan jika terjadi kombinasi transformasi ini dengan penggunaan instruksi monitor yang muncul jika ada blok synchronized dalam program asli. Transformasi ini digunakan oleh DashO dan ZKM. JBCO menggunakan variasi dari transformasi ini yang dikombinasikan dengan penambahan exception handling di daerah yang hanya berisi instruksi goto.
2.2.6 Menyimpan konstanta sebagai field
Referensi ke konstanta diubah menjadi referensi ke field, yang diinisialisasi dengan konstanta tersebut. Istilah ini diperkenalkan JBCO, namun ZKM melakukan transformasi ini sebagai bagian string encryption yang dilakukannya. Batchelder[1] menyatakan bahwa transformasi ini tampak tidak resilient, namun bila dikombinasikan dengan pembentukan predikat tersamarkan yang beberapa kali melakukan assignment pada field tersebut maka akan menjadi transformasi yang cukup resilient.
2.2.7 Menambahkan kode mati atau tidak relevan (Transformasi penambahan branch)
Transformasi ini menambahkan kode yang tidak relevan yang tidak akan pernah dilewati dalam eksekusi program. Kode yang ditambahkan dijaga oleh predikat tersamarkan (opaque predicate), sehingga decompiler ataupun manusia akan mengira kode tersebut adalah bagian dari program. Termasuk dalam kategori
2.2.8 Konversi Flow Graph dari Reducible menjadi Non-Reducible
Transformasi ini mengubah sebuah flow graph menjadi flow graph yang tidak bisa dinormalisasi, ini dilakukan dengan mengubah sebuah loop menjadi loop dengan titik masuk lebih dari satu. Dalam bahasa pemrograman Java loop dengan titik masuk lebih dari satu tidak bisa dibuat. Collberg et al. mengusulkan transformasi ini, dan logika yang serupa diterapkan ZKM pada rutin string decryptnya. Namun, Miecznikowski[17] menemukan bahwa flow graph yang non reducible bisa dibuat menjadi reducible dengan menambahkan control flag dan percabangan berdasarkan
control flag itu. Secara teoretis, Dava dapat mendecompile flow graph dengan titik
masuk lebih dari satu, tetapi untuk rutin string decrypt milik ZKM tidak dapat didecompile. Maka transformasi ini dinilai memiliki resiliensi tingkat menengah.
2.2.9 Menyamarkan panggilan ke library
Karena pemanggilan library tidak mengalami name obfuscation, nama-nama kelas dan method dari library menjadi titik tolak untuk reverse engineering. Collberg[7] et al. menyarankan untuk membuat library khusus ter-obfuscate untuk menggantikan library yang asli, sedangkan Batchelder [1] mengimplementasikan pembuatan library perantara ter-obfuscate secara dinamis. Implementasi ini dalam JBCO disebut Build
Library Buffer Class. Transformasi ini termasuk control obfuscation. Obfuscator