[Frida Labs] 10 - Calling a Native Function
Dalam bagian ini, kita akan mempelajari cara memanggil fungsi native menggunakan Frida.
Sebagai studi kasus, kita akan menerapkannya pada aplikasi Challenge 0xA.apk, yang bisa diunduh dari sini.
0x1 - Prerequisites
- Dasar reverse engineering menggunakan JADX.
- Kemampuan untuk memahami kode Java.
- Kemampuan untuk menulis kode Javascript.
- Familiar dengan ADB.
- Perangkat Android yang sudah di-root.
- Basics of x64/ARM64 assembly and reversing.
0x2 - Challenge 0xA
Mari kita mulai dengan menginstal aplikasi.
Seperti biasa, kita akan memulai dengan menggunakan JADX
.
APK ini mirip dengan sebelumnya. Dari analisis awal, kita melihat bahwa aplikasi memuat library frida0xa
di bagian bawah.
1
2
3
static {
System.loadLibrary("frida0xa");
}
Kita juga menemukan adanya metode JNI
di awal.
Metode ini mengembalikan string. Dalam aplikasi, metode dari library frida0xa.so
ini dipanggil untuk mengatur teks di TextView
, yang menampilkan teks “Hello Hackers”.
Selanjutnya, kita akan dekompilasi APK ini menggunakan apktool untuk memeriksa library-nya.
Setelah itu, kita akan memuat library ini ke dalam Ghidra untuk analisis lebih lanjut.
Dan setelah selesai dianalisis, kita lihat fungsi yang tersedia.
Kita menemukan dua fungsi:
get_flag()
Java_com_ad2001_frida0xa_MainActivity_stringFromJNI()
Kita tahu bahwa TextView
memanggil stringFromJNI()
untuk menampilkan “Hello Hackers”. Kamu bisa memverifikasinya dengan melihat dekompilasi.
Selanjutnya, ada metode get_flag()
, yang tidak dideklarasikan di Java space dan juga tidak dipanggil dari mana pun di library. Kita dapat memverifikasinya dengan melihat cross-reference-nya, yang hanya terdapat di tabel FDE (Frame Description Entry).
Melihat Cross-Reference get_flag()
Mari kita lihat dekompilasi metode ini.
Fungsi ini mengambil dua nilai integer, menjumlahkannya, dan memeriksa apakah hasilnya sama dengan 3. Jika iya, maka ia mendekode string FPE>9q8A>BK-)20A-#Y
dan menampilkan flag.
Jadi, untuk mendapatkan flag, kita perlu memanggil metode ini. Mari kita gunakan Frida untuk melakukannya.
0x3 - Calling the native function
Ini sangat mudah dilakukan. Berikut adalah template script:
1
2
3
var native_adr = new NativePointer(<address_of_the_native_function>);
const native_function = new NativeFunction(native_adr, '<return type>', ['argument_data_type']);
native_function(<arguments>);
Mari kita uraikan langkah demi langkah.
1
var native_adr = new NativePointer(<address_of_the_native_function>);
Untuk memanggil fungsi native dengan Frida, kita membutuhkan objek NativePointer
dengan alamat fungsi native. Setelah itu, kita membuat objek NativeFunction
, yang menjadi JavaScript wrapper untuk fungsi native.
1
NativeFunction(native_adr, '<return type>', ['argument_data_type']);
Argumen pertama haruslah objek NativePointer
, argumen kedua adalah tipe pengembalian dari fungsi native, argumen ketiga adalah daftar dari tipe data argumen yang akan diberikan ke fungsi native. Sekarang kita dapat memanggil metode tersebut seperti yang kita lakukan di Java space.
1
native_function(<arguments>);
Pertama, kita butuh alamat untuk get_flag
. Kita bisa menemukan offset menggunakan Ghidra dan menambahkannya ke base address libfrida0xa.so
.
1
➜ frida -U -f com.ad2001.frida0xa
1
2
Module.getBaseAddress("libfrida0xa.so")
# "0x7bc194e000"
Dengan mengurangi alamat ini dari base di gambar (0x0011dd60
), kita mendapatkan offset. Kita asumsikan bahwa Ghidra memuat binari pada 0x00010000
. Kita bisa menemukan basis ini di peta memori.
Kita dapat mendapatkan offset dengan mengurangi alamat ini dari base yang di gambar (0x0011dd60
). Secara default, ghidra memuat binari pada 0x00010000
. Kita dapat menemukan basis ini di Memory Map.
Dengan demikian, offset adalah 0x0011dd60
- 0x00100000
= 0x10dd60
. Sekarang kita bisa menambahkannya ke base.
1
2
Module.getBaseAddress("libfrida0xa.so") .add(0x1dd60)
# "0x7bc196bd60"
Sekarang kita punya alamatnya.
1
var adr = Module.findBaseAddress("libfrida0xa.so").add(0x1dd60)
Kemudian kita buat objek NativePointer
.
1
2
var adr = Module.findBaseAddress("libfrida0xa.so").add(0x1dd60) //Address of the get_flag() function
var get_flag_ptr = new NativePointer(adr);
Sekarang kita buat objek NativeFunction
. Argumen pertama adalah get_flag_adr
. Karena get_flag
adalah fungsi void
, kita gunakan void
. Fungsi get_flag
memiliki dua argumen integer, jadi kita gunakan ['int', 'int']
.
1
2
3
var adr = Module.findBaseAddress("libfrida0xa.so").add(0x1dd60) //Address of the get_flag() function
var get_flag_ptr = new NativePointer(adr);
const get_flag = new NativeFunction(get_flag_ptr, 'void', ['int', 'int']);
Untuk melewati pengecekan if
, kita perlu agar total penjumlahan argumen satu dan dua menjadi 3. Oleh karena itu, kita akan memasukkan nilai 1 untuk argumen pertama dan nilai 2 untuk argumen kedua, sehingga penjumlahan keduanya menghasilkan total 3.
1
2
3
4
5
6
7
8
9
10
11
f (param_1 + param_2 == 3) {
local_30 = 0;
while( true ) {
uVar1 = __strlen_chk("FPE>9q8A>BK-)20A-#Y",0xffffffff);
if (uVar1 <= local_30) break;
local_20[local_30] = "FPE>9q8A>BK-)20A-#Y"[local_30] + (char)(local_30 << 1);
local_30 = local_30 + 1;
}
local_d = 0;
__android_log_print(3,&DAT_0001bf62,"Decrypted Flag: %s",local_20);
}
Jadi, script final akan terlihat seperti berikut:
1
2
3
4
var adr = Module.findBaseAddress("libfrida0xa.so").add(0x1dd60) //Address of the get_flag() function
var get_flag_ptr = new NativePointer(adr);
const get_flag = new NativeFunction(get_flag_ptr, 'void', ['int', 'int']);
get_flag(1,2);
Mari kita jalankan frida dan coba script ini dan periksa log-nya.
Yay! Kita mendapatkan flag. Frida memang keren. :)