For English, please click here


ASLR

ASLR merupakan singkatan dari Address Space Layout Randomization, yaitu teknik yang digunakan oleh sistem operasi untuk mengamankan proses di user space terhadap eksploitasi dengan memory corruption. Ini dicapai dengan meletakkan alamat kode atau data pada sebuah proses di lokasi yang acak, sehingga seorang penyerang tidak tau dimana letaknya yang pasti. Pada sistem operasi modern seperti Linux, Windows, dan MacOS, letak alamat pada kernel juga diacak. Hal ini disebut dengan KASLR (Kernel Address Space Layout Randomization). Linux sendiri sudah menggunakan KASLR sejak 2014. Untuk mempermudah, blog ini akan menggunakan sistem operasi Linux (di x86), tetapi hal yang dipelajari mungkin dapat diterapkan di sistem lain.

Cara kerja ASLR (dan KASLR) adalah dengan mengacak lokasi dasar dari sebuah bagian memori. Jika bagian memori tersebut sebesar N page (1 page adalah 4096 byte pada sebagian besar sistem operasi), maka N page tersebut akan selalu berurutan, tetapi alamat dasarnya akan diacak. Sebagai contoh, jika sebuah proses membutuhkan sebesar 10 page, maka Linux akan memilih secara acak lokasi untuk page pertama, lalu menempatkan page sisanya berdasarkan urutan yang dibutuhkan proses tersebut. Hal ini mempermudah dan mempercepat penerapan ASLR, sehingga tidak melambatkan performa sistem terlalu drastis.

Terdapat berbagai cara untuk mengalahkan ASLR, yang paling sering digunakan adalah mendapatkan alamat dari satu lokasi pada sebuah bagian memori (sering disebut leak (bocoran)). Dikarenakan ASLR menempatkan semua page pada sebuah bagian memori sesuai dengan urutan yang dibutuhkan proses tersebut, maka dengan menambahkan atau mengurangi leak tersebut dengan nilai konstan, maka kita bisa mendapatkan alamat bagian kode manapun.

Catatan: Ini merupakan trik yang sangat basic yang digunakan di dunia binary exploitation, dan dapat dipelajari dengan menyelesaikan soal CTF yang simpel.

KASLR bersifat sama. Ketika komputer dinyalakan, sistem akan menempatkan beberapa bagian memori kernel pada lokasi tertentu. Sebagai catatan di (Linux) kernel terdapat berbagai bagian memori yang berbeda. Tujuan masing-masing bagian tersebut diluar cakupan blog ini, tapi garis besarnya dapat dilihat disini. Jadi, ketika ingin mengalahkan KASLR untuk bagian kernel text, kita mesti mendapatkan leak dari salah satu alamat di kernel text. Setelah itu, dengan menambakan atau mengurangi leak tersebut, maka seluruh kernel text dapat diketahui.

Biasanya, sebuah leak didapatkan dengan sebuah bug pada kernel. Contohnya adalah CVE-2024-26816. Akan tetapi, di blog ini kita bakal melihat sebuah “bug” pada CPU, khususnya CPU Intel dan AMD (x86).

Prefetch

Kalo diperhatikan, komputer zaman sekarang sudah luar biasa kencang. Alasan terbesar adalah kemajuan pada pembuatan komponen komputer, tapi alasan lain ada pada desain komponennya. Pada CPU sendiri, konsep seperti pipelining dan caching sangat membantu untuk meningkatkan performa CPU. Selain itu, terdapat berbagai trik pada level perangkat lunak yang dapat digunakan untuk meningkatkan performa. Salah satunya adalah menggunakan instruction set yang lebih kompleks seperti instruction set AVX, yang dapat melakukan komputasi dengan ukuran register yang lebih besar dalam satu instruksi.

Yang ingin kita dalami adalah instruksi PREFETCHh. Ini merupakan instruksi yang membantu meningkatkan performa dengan cara mengambil data dari memori dan menyimpannya di cache sebelum dilakukan instruksi load beneran. Cara kerjanya berbeda2 untuk masing-masing CPU, dan pembahasan mengenai cara prefetch diimplementasikan diluar cakupan blog ini (Ini sebenarnya alasan saja karena saya pun tidak ngerti).

Yang menarik dari instruksi PREFETCHh adalah ketiadaan pengecekan privilege level. Artinya, user biasa (unprivileged user) dapat melakukan prefetch pada alamat memory manapun, termasuk alamat memori kernel*. Ini bukan berarti data yang terdapat pada memori kernel dapat diakses (catatan: Meltdown & Spectre), tapi memori tersebut tetap akan diambil dan disimpan di cache CPU. Level cachenya bergantung pada variasi prefetch yang kita lakukan, dan perbedaan diluar cakupan blog ini.

Coba berpikir untuk sejenak mengenai paragraf sebelumnya. Seorang unprivileged user bisa prefetch alamat kernel, tapi ngak bisa diakses. Bagaimana bisa leak alamat kernel kalau ngak bisa diakses?

*Jika KPTI hidup, alamat memori kernel juga tidak dapat dilakukan prefetch. Pengecualikan ada pada CVE-2022-4543




Sidechannel

Kita bisa menggunakan sebuah teknik bernama sidechannel untuk mendapatkan leak alamat memori kernel. Menurut riset dari Gruss et.al, kita bisa mengukur waktu yang dibutuhkan untuk prefetch sebuah alamat memori kernel. Sebuah alamat memori yang valid akan berhasil dilakukan prefetch, sedangkan alamat memori yang invalid tidak akan berhasil. Oleh karena itu, jika dilakukan prefetch beberapa kali pada alamat yang sama, alamat memori yang valid membutuhkan waktu yang lebih singkat, disebabkan alamat memori tersebut sudah tersimpan pada cache CPU. Jadi, kita bisa mencoba seluruh kemungkinan kernel address (di x86, area .text hanya ada 512 kemungkinan!) dan melakukan prefetch pada alamat tersebut. Alamat yang valid akan berhasil di prefetch, dan waktu yang dibutuhkan akan lebih singkat dibanding alamat yang tidak valid.

Secara singkat, pengukuran lamanya prefetch dapat dilakukan dengan potongan kode berikut (dari https://www.willsroot.io/2022/12/entrybleed.html):

uint64_t sidechannel(uint64_t addr) {
    uint64_t a, b, c, d;
    asm volatile(".intel_syntax noprefix;"
                 "mfence;"
                 "lfence;"
                 "rdtsc;"
                 "mov %0, rax;"
                 "mov %1, rdx;"
                 "xor rax, rax;"
                 "lfence;"
                 "prefetchnta qword ptr [%4];"
                 "prefetcht2 qword ptr [%4];"
                 "xor rax, rax;"
                 "lfence;"
                 "rdtsc;"
                 "mov %2, rax;"
                 "mov %3, rdx;"
                 "mfence;"
                 ".att_syntax;"
                 : "=r"(a), "=r"(b), "=r"(c), "=r"(d)
                 : "r"(addr)
                 : "rax", "rbx", "rcx", "rdx");
    a = (b << 32) | a;
    c = (d << 32) | c;
    return c - a; // timing difference
}

Kode untuk mendapatkan leak alamat memori kernel secara lengkap dapat dicari pada sumber lain, atau menjadi bahan latihan.

Latihan

Coba buat / minta AI untuk buat sebuah challenge CTF, dimana user diberikan cara untuk menjalankan shellcode dan mesti mencari cara untuk membaca flag yang disimpan pada sebuah alamat random. Gunakan prefetch attack buat leak alamat flagnya, lalu cetak flagnya.

Terdapat juga challenge di Dreamhack dengan konsep yang sama.

Penutup

Bila tertarik pelajari lebih lanjut, kalian bisa mendalami:

  • Meltdown
  • Spectre
  • KPTI
  • Entrybleed


Dan diluar ranah binary exploitation:

  • Timing attacks di kriptografi


Terima kasih sudah membaca

Referensi