KAPOLRI CUP 2026: Crackme Polri
CrackMe Polri — SMC (Self-Modifying Code)
Kategori: Reverse Engineering
File: crackme_polri (ELF x86-64, PIE, stripped)
Flag: polriCTF26{SMC_1S_S0_C00L!!}
Ringkasan
Crackme ELF x86-64 stripped + PIE. Checker flag asli nggak ada di file sebagai kode: dia terenkripsi (XOR) dan baru didekripsi + dieksekusi dari buffer mmap saat runtime (SMC). Ada juga fungsi 0x1420 yang kelihatan seperti checker tapi cuma decoy (nggak pernah dipanggil). Jadi langkahnya: temukan loop decrypt di main, tiru decrypt-nya offline, disassemble, balik transformasinya.
Langkah-Langkah
1. Recon
file crackme_polri
# ELF 64-bit LSB pie executable, x86-64, stripped
readelf -l crackme_polri | head # entry point 0x1330
strings crackme_polri | head
PIE → semua alamat di objdump/Ghidra itu offset; saat runtime dikasih base acak (ASLR).
2. Disassemble main
Entry point 0x1330 manggil 0x1160 (main). Disassemble:
objdump -d --start-address=0x1160 --stop-address=0x1300 crackme_polri
Inti main (decompile Ghidra ringkas):
if (argc != 2) { printf("Usage: %s <flag>", argv[0]); return 1; }
strncpy(buf, argv[1], 0x7f); // salin flag
if (strlen(buf) == 0x1c // total 28 char
&& strncmp(buf, "polriCTF26{", 0xb) == 0 // prefix 11 char
&& buf[0x1b+0x10] == '}') { // offset 27 = '}'
// 16 byte tengah flag diambil (offset 11..26)
__addr = mmap(0, 0x160, PROT_READ|WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
// loop decrypt 0x160 byte
// mprotect(RX)
ok = (*__addr)(&inner16); // panggil checker asli
if (ok) puts("Correct! Flag valid.");
else puts("Nope, coba lagi.");
}
Jadi main cuma: validasi format → decrypt shellcode → call. Yang bikin flag valid ditentukan sepenuhnya oleh hasil decrypt.
3. Loop Decrypt (inti SMC)
0x1246: lea r8, [rip+0xe33] # -> 0x2080 (key)
0x1250: lea rdi, [rip+0xe4c] # -> 0x20a0 (payload terenkripsi, 0x160 byte)
0x1270: loop 0x160 kali:
dec[i] = key[i % 5] ^ payload[i]
0x12af: mprotect(buf, 0x160, PROT_READ|PROT_EXEC)
0x12bd: call rbx # eksekusi hasil decrypt
Ekspresi i % 5 di asm ditulis pake trik magic-number mul 0xcccccccccccccccd + and ~3 + shr 2 — cuma compiler biasa. Yang penting hasilnya i % 5.
Cek isi key di file:
offset 0x2080: 91 2c e7 4b 18 00 00 00 00 00
Karena indeksnya i % 5, key efektif cuma 5 byte berulang: [0x91, 0x2c, 0xe7, 0x4b, 0x18].
4. Decrypt Offline
data = open("crackme_polri", "rb").read()
key = data[0x2080:0x2085] # 91 2c e7 4b 18
enc = data[0x20a0:0x20a0+0x160]
dec = bytes(enc[i] ^ key[i % 5] for i in range(0x160))
open("dec.bin", "wb").write(dec)
5. Disassemble Hasil Decrypt
objdump -D -b binary -m i386:x86-64 dec.bin
Bentuk checker asli (singkat):
; rbp-0x28 = pointer ke 16 byte flag dalam
for i = 0 .. 15:
c = flag[i]
c ^= 0x5a
c += 3*i ; per byte, mod 256
c = rol2(c) ; rotate left 2 bit : (c<<2)|(c>>6)
buf[i] = c
; lalu 16x compare, contoh:
cmp byte [buf+0], 0x24
cmp byte [buf+1], 0x68
...
cmp byte [buf+15], 0xa2
Target 16 byte-nya:
24 68 7c 38 dd 60 5c 78 0a 80 dc 2e 3a f4 96 a2
6. Balikkan Transformasi
Forward:
c = rol2( (flag[i] ^ 0x5a) + 3*i ) (mod 256) == target[i]
Inverse:
c = ror2( target[i] )
c = (c - 3*i) & 0xFF
flag[i] = c ^ 0x5a
def ror2(b): return ((b >> 2) | ((b << 6) & 0xFF)) & 0xFF
targets = [0x24,0x68,0x7c,0x38,0xdd,0x60,0x5c,0x78,
0x0a,0x80,0xdc,0x2e,0x3a,0xf4,0x96,0xa2]
inner = "".join(chr(((ror2(targets[i]) - 3*i) & 0xFF) ^ 0x5a) for i in range(16))
# SMC_1S_S0_C00L!!
7. Verifikasi
./crackme_polri 'polriCTF26{SMC_1S_S0_C00L!!}'
# Correct! Flag valid.
Catatan: Fungsi Decoy 0x1420
FUN_00101420 (offset 0x1420) kelihatan seperti checker: load 16 byte, paddb/pxor/rotate, lalu cmp 8 konstanta (0x77,0x12,0x99,0x03,0x5e,0xc1,0x2a,0x8d). Tapi:
- Di ghidra/objdump,
0x1420tidak pernah di-call(0 caller). mainmanggil checker lewatcall *%rbx(pointer hasil decrypt), bukan0x1420.
Jadi fungsi itu umpan. Verifikasinya: klik 0x1420 di Ghidra → References → Show References to → kosong.
Solver Lengkap
crackme_polri_solve.py — decrypt SMC, ambil target dari disassembly, balik transformasi, cek ke server/binary.
Kesimpulan
- Checker asli = kode XOR-encrypted di
0x20a0, key[0x91,0x2c,0xe7,0x4b,0x18](i%5), ukuran0x160. - Transformasi flag:
rol2((c ^ 0x5A + 3*i) & 0xFF)dibanding ke 16 konstanta. - Balik transformasi →
SMC_1S_S0_C00L!!.
Flag
polriCTF26{SMC_1S_S0_C00L!!}