
Baby RE adalah tantangan CTF kategori Reversing dengan tingkat kesulitan easy dari HTB Challenges. Tantangan ini dibuat oleh Xh4H dan telah dirilis pada 26 Oktober 2019.
Berikut adalah deskripsi dari tantangan ini:
Show us your basic skills! (P.S. There are 4 ways to solve this, are you willing to try them all?)
Pendahuluan
Setelah mengesktrak archive tantangan, saya menemukan satu file bernama baby
.
➜ ls -lahtotal 36Kdrwxrwxr-x 3 kali kali 4.0K Mar 1 03:48 .drwxrwxr-x 4 kali kali 4.0K Mar 1 03:36 ..-rw-rw-r-- 1 kali kali 17K Oct 13 2019 baby
➜ file babybaby: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=25adc53b89f781335a27bf1b81f5c4cb74581022, for GNU/Linux 3.2.0, not stripped
File ini merupakan ELF 64-bit LSB pie executable
yang merupakan file executable Linux.
Ketika saya menjalankan program ini, saya diminta untuk menginputkan key. Jika saya menginputkan sembarang teks, misalnya “test”, program akan menampilkan pesan “Try again later” lalu berhenti. Hal ini menunjukkan bahwa program hanya menerima key yang valid.
➜ chmod +x baby
➜ ./babyInsert key:testTry again later.
Proof of Concept
Pada deskripsi tantangan disebutkan bahwa terdapat empat cara untuk menyelesaikan tantangan ini. Namun, saya hanya menemukan tiga cara, sebagaimana yang tercantum pada walkthrough resmi, yaitu:
- Menggunakan
strings
- Analisis dinamis menggunakan
ltrace
- Menggunakan decompiler
1. Menggunakan strings
Tantangan ini meminta saya untuk mendapatkan key yang valid agar bisa mendapatkan flag.
Langkah sederhana yang bisa saya lakukan adalah menggunakan strings
untuk menampilkan semua string yang ada di dalam file executable tersebut.
Baca lebih lanjut tentang
strings
di sini.
➜ strings baby/lib64/ld-linux-x86-64.so.2mgUalibc.so.6putsstdinfgets__cxa_finalizestrcmp__libc_start_mainGLIBC_2.2.5_ITM_deregisterTMCloneTable__gmon_start___ITM_registerTMCloneTableu/UHHTB{B4BYH_R3V_TH4HTS_Ef[]A\A]A^A_Dont run `strings` on this challenge, that is not the way!!!!Insert key:abcde122313Try again later.;*3$"GCC: (Debian 9.2.1-8) 9.2.1 20190909crtstuff.cderegister_tm_clones
Hasilnya, saya menemukan beberapa string yang menarik pada file baby
, seperti:
- Terdapat potongan flag di sini:
HTB{B4BYH_R3V_TH4HTS_Ef
- Terdapat string “
abcde122313
” di antara “Insert Key” dan “Try again later”. Saya menduga ini adalah key yang dikomparasikan dengan inputan pengguna. Jika inputan salah, program akan menampilkan pesan “Try again later”.
Dari hasil analisis ini, saya mencoba menginputkan “abcde122313
” sebagai key, dan hasilnya program menampilkan flag.
➜ ./babyInsert key:abcde122313HTB{B4BY_R3V_TH4TS_EZ}
2. Analisis dinamis menggunakan ltrace
Pada saat membuat program, ketika kita ingin melakukan perbandingan inputan biasanya menggunakan pengkondisian seperti if
atau switch
.
Sebagian besar file executable di Linux menggunakan libc
(C Library) karena library ini menyediakan fungsi dasar (API) untuk berinteraksi dengan kernel dan sistem operasi.
Dalam bahasa C, terdapat fungsi strcmp()
dan strncmp()
untuk membandingkan dua string.
Untuk mengetahui apakah program tersebut memanggil shared library (seperti libc
), saya mencoba menggunakan ltrace
.
Baca lebih lanjut tentang
ltrace
di sini.
➜ ltrace ./babyputs("Insert key: "Insert key:) = 13fgets(test"test\n", 20, 0x7f3358e1a8e0) = 0x7ffe6545af20strcmp("test\n", "abcde122313\n") = 13puts("Try again later."Try again later.) = 17+++ exited (status 0) +++
Ketika saya menginputkan “test”, ltrace
mendeteksi bahwa program melakukan pemanggilan fungsi berikut:
strcmp("test\n", "abcde122313\n")
Program ini membandingkan inputan pengguna dengan “abcde122313”, yang di mana ini adalah key yang valid untuk mendapatkan flag.
3. Analisis statik menggunakan decompiler
Dekompilasi program baby
menggunakan Ghidra menghasilkan fungsi main()
seperti berikut:
undefined8 main(void)
{ int iVar1; undefined8 local_48; undefined8 local_40; undefined4 local_38; undefined2 local_34; char local_28 [24]; char *local_10;
local_10 = "Dont run `strings` on this challenge, that is not the way!!!!"; puts("Insert key: "); fgets(local_28,0x14,stdin); iVar1 = strcmp(local_28,"abcde122313\n"); if (iVar1 == 0) { local_48 = 0x594234427b425448; local_40 = 0x3448545f5633525f; local_38 = 0x455f5354; local_34 = 0x7d5a; puts((char *)&local_48); } else { puts("Try again later."); } return 0;}
Baca lebih lanjut tentang Ghidra di sini.
Dari sini sebenarnya kita sudah mengetahui jawabannya, terdapat perbandingan inputan pengguna dan valid key pada baris kode ke-15.
Analisis Kode
Meskipun saya sudah mendapatkan jawaban dari tantangan ini, saya ingin menganalisis hasil dekompilasi tersebut lebih dalam untuk mempelajarinya secara lebih detail.
- Identifikasi fungsi
main()
undefined8 main(void)
{
return 0;}
- Fungsi ini tidak menerima argument (
s
). - Tipe data
undefined8
seharusnya adalahint
, seperti yang kita lihat pada baris pengembalian nilai:
return 0;
- Deklarasi variabel
int iVar1;undefined8 local_48;undefined8 local_40;undefined4 local_38;undefined2 local_34;char local_28 [24];char *local_10;
Mari kita identifikasi masing-masing variabel:
Variabel | Tipe Data | Fungsi |
---|---|---|
iVar1 | int | Menyimpan hasil dari strcmp() . |
local_48 , local_40 , local_38 , local_34 | undefined8 , undefined4 , undefined2 | Berisi flag dalam hex. |
local_28 | char array | Menyimpan input dari pengguna. |
local_10 | char * | Menyimpan string yang tidak pernah ditampilkan (tersembunyi). |
- String tersembunyi
local_10 = "Dont run `strings` on this challenge, that is not the way!!!!";
- Menyimpan string ke dalam variabel
local_10
dan tidak pernah ditampilkan. - Ini biasanya digunakan sebagai petunjuk kepada peserta untuk tidak menggunakan
strings
dalam menyelesaikan tantangan.
- Meminta pengguna untuk menginputkan key
puts("Insert key: ");fgets(local_28,0x14,stdin);
puts()
akan menampilkan string ke layar.fgets()
akan menyimpan inputan pengguna ke dalam variabellocal_28
dengan maksimal 20 karakter (0x14
).
- Membandingkan input pengguna dengan key
iVar1 = strcmp(local_28,"abcde122313\n");
- Fungsi
strcmp()
membandingkan inputan pengguna dengan “abcde122313”. Jika cocok, nilai yang akan dikembalikan bernilai 0. - Hasil perbandingan tersebut akan disimpan dalam variabel
iVar1
.
- Jika input benar, flag ditampilkan
if (iVar1 == 0) { local_48 = 0x594234427b425448; // "YB4B{BTH" ➜ "HTB{B4BY" local_40 = 0x3448545f5633525f; // "4HT_VER_" ➜ "_REV_TH4" local_38 = 0x455f5354; // "E_ST" ➜ "TS_E" local_34 = 0x7d5a; // "}Z" ➜ "Z}" puts((char *)&local_48);}
if (iVar1 == 0)
: Jika variabeliVar1
bernilai 0, maka flag akan ditampilkan.- Flag dipecah menjadi 4 bagian, setiap bagian memiliki kapasitas maksimal
8 byte
.- Hal ini terjadi karena arsitektur CPU 64-bit (x86-64) lebih efisien membaca data dalam unit 64-bit (
8 byte
).
- Hal ini terjadi karena arsitektur CPU 64-bit (x86-64) lebih efisien membaca data dalam unit 64-bit (
- Little Endian (dibalik).
- CPU x86-64 menggunakan Little Endian, sehingga data hex yang tersimpan di memori harus dibalik agar terbaca dengan benar.
Hexadecimal | ASCII (Terbalik) | ASCII (Setelah Dibalik) |
---|---|---|
0x594234427b425448 | "YB4B{BTH" | "HTB{B4BY" |
0x3448545f5633525f | "4HT_VER_" | "_REV_TH4" |
0x455f5354 | "E_ST" | "TS_E" |
0x7d5a | "}Z" | "Z}" |
# Contoh pembacaan Little Endian untuk 0x594234427b42544848 ➜ 'H'54 ➜ 'T'42 ➜ 'B'7b ➜ '{'42 ➜ 'B'34 ➜ '4'42 ➜ 'B'59 ➜ 'Y'
Berikut adalah jika pseudocode tersebut ditulis ulang ke dalam kode C yang lebih readable:
if (strcmp(user_input, "abcde122313\n") == 0) { char flag[] = "HTB{B4BY_REV_TH4TS_EZ}"; puts(flag);}
Penutup
Itulah cara menyelesaikan tantangan Baby RE dari HTB Challenges dengan menggunakan teknik analisis statik dan dinamis. Semoga bermanfaat!