GEMASTIK XIX: Hexlock Reverse Engineering
Write-up Reverse Engineering GEMASTIK XIX: Hexlock
Ringkasan Challenge
| Informasi | Nilai |
|---|---|
| Event | GEMASTIK XIX |
| Kategori | Reverse Engineering |
| Binary | hexlock |
| Format | ELF 64-bit, x86-64, statically linked, stripped |
| Bahasa | Go |
| Proteksi | Rekonstruksi key saat runtime dan korupsi key melalui anti-debug |
| Kriptografi | AES-128-GCM |
| Flag | GEMASTIK19{6_sh4rd5_r34ss3mbl3_th3_g0ph3r5_s3cr3t} |
Write-up ini mengasumsikan file challenge tersedia sebagai
./hexlock. File awal memiliki ekstensi.txt, tetapi magic bytes-nya menunjukkan bahwa file tersebut merupakan executable ELF.
1. Recon Awal
Jangan menentukan jenis file hanya berdasarkan ekstensi. Periksa magic bytes dan metadata ELF terlebih dahulu.
cp 'hexlock (this is file not txt).txt' hexlock
chmod +x hexlock
file hexlock
sha256sum hexlock
readelf -h hexlock
readelf -S hexlock
Informasi penting yang diperoleh:
ELF 64-bit LSB executable, x86-64, statically linked, stripped
Empat byte pertama file adalah magic ELF:
7f 45 4c 46
Daftar section juga memuat beberapa section khas Go:
.go.buildinfo
.gopclntab
.gosymtab
.typelink
.itablink
Cari indikator tambahan dengan:
strings -a -n 5 hexlock | grep -E \
'go.buildinfo|gopclntab|runtime\.|crypto/aes|crypto/cipher|flag>|Correct!|Wrong\.'
Banyaknya string runtime Go, static linkage, dan metadata Go menunjukkan bahwa binary ini dibuat dengan Go. Karena binary sudah stripped dan standard library Go ikut ter-link secara statis, hasil strings dan disassembly sangat ramai. Analisis perlu difokuskan ke string aplikasi dan alur yang menggunakannya.
2. Mencari String Antarmuka
Binary menampilkan tiga string yang sangat membantu:
flag>
Wrong.
Correct!
Cari file offset masing-masing string:
grep -aob 'flag> ' hexlock
grep -aob 'Wrong.' hexlock
grep -aob 'Correct!' hexlock
Hasilnya:
flag> 0xd3607
Wrong. 0xd360d
Correct! 0xd3755
Section .rodata dipetakan pada:
Virtual address : 0x4ae000
File offset : 0xae000
Rumus konversi file offset ke virtual address:
VA = section_VA + (file_offset - section_file_offset)
Contoh untuk string Correct!:
0x4ae000 + (0xd3755 - 0xae000) = 0x4d3755
Go biasanya merepresentasikan string menggunakan descriptor yang berisi pointer menuju data dan panjang string. Karena itu, kode belum tentu mereferensikan byte string secara langsung. Pencarian pointer little-endian ke alamat string membawa kita ke descriptor, lalu xref descriptor membawa ke fungsi utama aplikasi.
Fungsi validasi utama berada di sekitar:
0x4ace40
Dump fungsi tersebut:
objdump -d -M intel \
--start-address=0x4ace40 \
--stop-address=0x4ad120 \
hexlock > main.asm
less main.asm
Alamat penting yang diperoleh:
0x4ace40 fungsi validasi utama
0x4acba0 pembentuk key
0x4acd40 wrapper enkripsi
0x4ad040 pemrosesan hasil enkripsi
0x4ad0ae loop perbandingan byte
Jika menggunakan Ghidra, tekan G, masukkan alamat, lalu rename fungsi berdasarkan perilakunya:
0x4ace40 -> main_like
0x4acba0 -> build_key
0x4acd40 -> encrypt_input
Pada tahap ini fungsi encrypt_input belum perlu langsung disebut AES-GCM. Algoritmanya harus dibuktikan dari data flow.
3. Merekonstruksi Alur Validasi
Fungsi pada 0x4ace40 melakukan alur berikut:
- Menampilkan prompt
flag>. - Membaca satu baris input.
- Menghapus newline di akhir input.
- Membentuk key sepanjang 16 byte.
- Mengenkripsi input pengguna.
- Membandingkan hasil enkripsi dengan target global.
- Menampilkan
Correct!atauWrong..
Bagian perbandingan dapat dilihat dengan:
objdump -d -M intel \
--start-address=0x4ad030 \
--stop-address=0x4ad117 \
hexlock
Instruksi penting:
4ad040: call 0x4acd40
4ad045: test rax,rax
4ad048: je 0x4ad062
4ad04a: cmp QWORD PTR [rip+...],rbx # panjang target
4ad051: jne 0x4ad062
4ad053: mov rdx,QWORD PTR [rip+...] # pointer target
4ad05a: xor ecx,ecx
4ad05c: xor esi,esi
4ad0ae: movzx r8d,BYTE PTR [rax+rcx]
4ad0b3: movzx r9d,BYTE PTR [rdx+rcx]
4ad0b8: xor r8d,r9d
4ad0bb: inc rcx
4ad0be: or esi,r8d
4ad0c1: cmp rbx,rcx
4ad0c4: jg 0x4ad0ae
4ad0c6: test sil,sil
4ad0c9: jne 0x4ad062
Pseudocode-nya:
encrypted = encrypt_input(user_input, &encrypted_len);
if (encrypted == NULL)
wrong();
if (encrypted_len != expected_len)
wrong();
difference = 0;
for (i = 0; i < encrypted_len; i++)
difference |= encrypted[i] ^ expected[i];
if (difference != 0)
wrong();
correct();
Jadi input tidak dibandingkan secara langsung dengan flag. Binary mengenkripsi kandidat input terlebih dahulu, kemudian membandingkan output autentikasinya dengan buffer target.
4. Membuktikan Penggunaan AES-GCM
Dump wrapper enkripsi:
objdump -d -M intel \
--start-address=0x4acd40 \
--stop-address=0x4ace40 \
hexlock > encrypt.asm
less encrypt.asm
Data flow menunjukkan karakteristik berikut:
Panjang key = 16 byte
Panjang nonce = 12 byte
Overhead output = 16 byte
Additional data = kosong
Nilai-nilai ini cocok dengan AES-GCM standar pada Go:
- Key AES 16 byte berarti AES-128.
- GCM standar menggunakan nonce 12 byte.
- GCM standar menambahkan authentication tag 16 byte.
AEAD.Sealpada Go menghasilkanciphertext || tag.
Target memiliki panjang 66 byte. Karena tag GCM berukuran 16 byte:
Panjang plaintext = 66 - 16
= 50 byte
Konsep kode Go yang direkonstruksi:
block, err := aes.NewCipher(key)
if err != nil {
return nil
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil
}
sealed := gcm.Seal(nil, nonce, input, nil)
Dengan demikian, algoritma yang digunakan adalah AES-128-GCM dengan AAD kosong.
5. Mengambil Target Ciphertext
Loop perbandingan mengakses descriptor slice Go global:
0x56f490 pointer data
0x56f498 length
0x56f4a0 capacity
Representasi slice Go pada target ini dapat dibaca sebagai:
struct GoSlice {
void *data;
uint64_t len;
uint64_t cap;
};
Melalui GDB
gdb -q ./hexlock
Di dalam GDB:
set pagination off
starti
x/3gx 0x56f490
Descriptor tersebut menghasilkan:
data = 0x567660
len = 0x42
cap = 0x42
0x42 sama dengan 66 desimal. Dump tepat 66 byte:
dump binary memory target.bin 0x567660 0x5676a2
quit
Alamat akhir bersifat eksklusif:
0x567660 + 0x42 = 0x5676a2
Tampilkan sebagai satu baris hex:
od -An -v -tx1 target.bin | tr -d ' \n'
echo
Target yang diperoleh:
4ea13b5cc7a00ff3b8f3654beabe5bcc83a4c1710772371126e70c45528d8a7e1e2c472e373bd87fd6e68247c3a865e2b3ed12fc30641557730e00a28224efdb8594
Dua byte terakhir 8594 tetap merupakan bagian target 66 byte, bukan field terpisah. Parsing yang benar:
ciphertext = target[:-16]
tag = target[-16:]
Mengambil Langsung dari ELF
Mapping section .data:
.data VA = 0x56efc0
.data file offset = 0x16efc0
Konversi alamat target ke file offset:
0x16efc0 + (0x567660 - 0x56efc0) = 0x167660
Extract tanpa GDB:
dd if=hexlock \
of=target.bin \
bs=1 \
skip=$((0x167660)) \
count=66 \
status=none
6. Mengambil Nonce
Wrapper enkripsi memuat pointer 0x567148 dan panjang 0x0c:
Alamat nonce = 0x567148
Panjang = 0x0c = 12 byte
Melalui GDB
x/12bx 0x567148
dump binary memory nonce.bin 0x567148 0x567154
Nonce yang diperoleh:
194b00b0922d969e007055a4
Langsung dari ELF
Konversi virtual address ke file offset:
0x16efc0 + (0x567148 - 0x56efc0) = 0x167148
Extract:
dd if=hexlock \
bs=1 \
skip=$((0x167148)) \
count=12 \
status=none | od -An -v -tx1 | tr -d ' \n'
echo
Output:
194b00b0922d969e007055a4
7. Analisis Pembentuk Key
Fungsi 0x4acba0 membangun key 16 byte dari beberapa shard.
objdump -d -M intel \
--start-address=0x4acba0 \
--stop-address=0x4acd40 \
hexlock > key_builder.asm
less key_builder.asm
Pada awal fungsi, area lokal 16 byte dibersihkan:
movups XMMWORD PTR [rsp+0x20],xmm15
Artinya buffer key lokal dimulai dari:
rsp + 0x20
Instruksi seperti:
mov BYTE PTR [rsp+rax+0x20],bl
setara dengan:
key[rax] = bl;
Contoh Transformasi Shard Pertama
Salah satu shard di-decode dengan XOR:
movzx ebx,BYTE PTR [rdx+rax]
xor ebx,0x5a
mov BYTE PTR [rsp+rax+0x20],bl
Pseudocode:
key[i] = shard[i] ^ 0x5a;
Contoh Transformasi Shard Kedua
Shard lain menggunakan transformasi affine:
movzx ebx,BYTE PTR [rdx+rax]
lea esi,[rbx+rbx*2]
lea ebx,[rbx+rsi*2]
lea ebx,[rbx+0x3d]
Perhitungannya:
esi = x + 2x = 3x
ebx = x + 2(3x) = 7x
ebx = 7x + 0x3d
Pseudocode:
key_part[i] = (7 * shard[i] + 0x3d) & 0xff;
Rekonstruksi Berbasis Bit
Tabel lain digunakan untuk membentuk byte satu bit demi satu bit:
movzx ebx,BYTE PTR [rbx+rsi]
and ebx,0x1
shl ebx,cl
or edx,ebx
Pseudocode:
value = 0;
for (bit = 0; bit < 8; bit++)
value |= (table[index] & 1) << bit;
Fungsi lengkap menggabungkan beberapa bagian seperti ini. Hal tersebut sesuai dengan isi flag yang menyebut enam shard sedang merakit kembali sebuah rahasia.
8. Menemukan Anti-Debug dan Mengambil Key yang Benar
Di dalam pembentuk key terdapat pemanggilan helper anti-debug:
4acc45: call 0x4ac700
4acc4a: test al,al
4acc4c: je 0x4acc5a
4acc4e: movzx eax,BYTE PTR [rsp+0x25]
4acc53: xor eax,0x11
4acc56: mov BYTE PTR [rsp+0x25],al
Karena key dimulai dari rsp+0x20, byte yang dimodifikasi adalah:
(rsp + 0x25) - (rsp + 0x20) = key[5]
Pseudocode:
if (anti_debug_check())
key[5] ^= 0x11;
Artinya, key yang diambil langsung ketika debugger terdeteksi dapat sengaja dirusak.
Caller memanggil pembentuk key di:
4ad015: call 0x4acba0
4ad01a: movups xmm0,XMMWORD PTR [rsp]
Sesaat setelah fungsi kembali, hasil 16 byte tersedia pada $rsp milik caller.
Mengambil Key Normal dengan GDB
gdb -q ./hexlock
Pasang dua breakpoint:
set pagination off
set confirm off
set disassembly-flavor intel
break *0x4acc4a
break *0x4ad01a
run
Ketika prompt muncul, masukkan tepat 50 karakter dummy:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Breakpoint pertama berada tepat setelah fungsi anti-debug kembali. Paksa hasil boolean di AL menjadi false:
p/x $al
set $al = 0
p/x $al
continue
Ketika berhenti di 0x4ad01a, dump key 16 byte:
x/16bx $rsp
dump binary memory key.bin $rsp $rsp+16
quit
Tampilkan key:
od -An -v -tx1 key.bin | tr -d ' \n'
echo
Key normal:
7f8a7205095edb11515d302c2695808a
Byte normal pada indeks kelima adalah 0x5e. Jika cabang anti-debug dijalankan:
0x5e ^ 0x11 = 0x4f
Key palsu yang dihasilkan:
7f8a7205094fdb11515d302c2695808a
Penggunaan key palsu akan membuat verifikasi authentication tag AES-GCM gagal.
9. Material Kriptografi yang Berhasil Direcover
Algoritma : AES-128-GCM
Key : 7f8a7205095edb11515d302c2695808a
Nonce : 194b00b0922d969e007055a4
AAD : kosong
Target : ciphertext || authentication tag 16 byte
Target lengkap:
4ea13b5cc7a00ff3b8f3654beabe5bcc83a4c1710772371126e70c45528d8a7e1e2c472e373bd87fd6e68247c3a865e2b3ed12fc30641557730e00a28224efdb8594
10. Solver
Install PyCryptodome jika belum tersedia:
python3 -m pip install pycryptodome
Buat solve.py:
from Crypto.Cipher import AES
TARGET = bytes.fromhex(
"4ea13b5cc7a00ff3b8f3654beabe5bcc"
"83a4c1710772371126e70c45528d8a7e"
"1e2c472e373bd87fd6e68247c3a865e2"
"b3ed12fc30641557730e00a28224efdb"
"8594"
)
KEY = bytes.fromhex(
"7f8a7205095edb11515d302c2695808a"
)
NONCE = bytes.fromhex(
"194b00b0922d969e007055a4"
)
ciphertext = TARGET[:-16]
tag = TARGET[-16:]
cipher = AES.new(KEY, AES.MODE_GCM, nonce=NONCE)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
flag = plaintext.decode()
assert len(plaintext) == 50
assert flag.startswith("GEMASTIK19{")
assert flag.endswith("}")
print(flag)
Jalankan:
python3 solve.py
Output:
GEMASTIK19{6_sh4rd5_r34ss3mbl3_th3_g0ph3r5_s3cr3t}
11. Verifikasi ke Binary Asli
printf '%s\n' \
'GEMASTIK19{6_sh4rd5_r34ss3mbl3_th3_g0ph3r5_s3cr3t}' |
./hexlock
Output yang diharapkan:
flag> Correct!
Dekripsi yang lolos autentikasi dan respons Correct! dari binary menjadi dua verifikasi independen bahwa flag benar.
12. Flag
GEMASTIK19{6_sh4rd5_r34ss3mbl3_th3_g0ph3r5_s3cr3t}
13. Referensi dan Challenge Serupa
Write-up CTF Serupa
-
Challenge reverse engineering Go yang sudah stripped. Write-up tersebut membahas pemulihan simbol Go, penggunaan Ghidra, referensi AES-GCM, dan inspeksi data di sekitar fungsi enkripsi. Ini merupakan referensi yang paling dekat secara konsep dengan
hexlock. -
BTCTF 2024: golang2 Reverse Engineering
Write-up reverse engineering binary Go yang membahas kesulitan static linkage serta penggunaan tooling seperti GoReSym dan integrasi dengan Ghidra atau IDA.
Reverse Engineering Binary Go
-
CUJO AI: Reverse Engineering Go Binaries with Ghidra
Penjelasan mengenai tantangan analisis binary Go, termasuk static linkage, hilangnya nama fungsi, metadata Go, dan penggunaan script Ghidra.
-
Tool untuk memulihkan metadata dari binary Go, termasuk informasi fungsi, source filename, line metadata, embedded structures, dan type.
-
Go Reverse Engineering Tool Kit
Koleksi tool untuk menganalisis binary Go, termasuk GoRE, Redress, Libgore, dan PyGoRE.
-
Kumpulan Catatan dan Tool Reverse Engineering Go
Kumpulan metode, tool, dan referensi untuk reverse engineering executable Go.
Perilaku AES-GCM
-
Dokumentasi interface
AEAD,NonceSize,Overhead,Seal, danOpen. FungsiSealmengenkripsi sekaligus mengautentikasi plaintext, lalu menambahkan hasilnya ke destination slice. -
Dokumentasi bahwa AES menerima key 16, 24, atau 32 byte untuk memilih AES-128, AES-192, atau AES-256.
-
Implementasi GCM Go mendefinisikan nonce standar 12 byte dan authentication tag 16 byte. Ukuran tersebut sama dengan nilai yang ditemukan pada
hexlock.
14. Ringkasan Alur Analisis
Urutan yang dapat digunakan untuk challenge stripped Go serupa:
identifikasi file
-> fingerprint metadata dan runtime Go
-> cari string aplikasi
-> cari descriptor string dan xref
-> temukan fungsi validasi utama
-> analisis transformasi input atau wrapper crypto
-> analisis loop perbandingan output
-> recover global Go slice
-> telusuri data flow key dan nonce
-> periksa anti-debug
-> lakukan dekripsi terautentikasi secara offline
-> verifikasi hasil ke binary
Alur ini menjaga analisis tetap berbasis bukti dan mencegah kesimpulan algoritma hanya berdasarkan string yang ditemukan.