Writeup Aria
C2C QualificationReverse Engineering

bunaken

1771214529457

description

Author: vidner

Can you help me to recover the flag?

Attachments

unzip bunaken_bunaken-dist.zip
# Archive:  bunaken_bunaken-dist.zip
#   inflating: bunaken
#   inflating: flag.txt.bunakencrypted

ls
# bunaken  bunaken_bunaken-dist.zip  flag.txt.bunakencrypted

file *
# bunaken:                  ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=109a021c1b3405d73bd0e95dcad52ec5857f4ed9, not stripped
# bunaken_bunaken-dist.zip: Zip archive data, at least v2.0 to extract, compression method=deflate
# flag.txt.bunakencrypted:  ASCII text, with no line terminators

Overview

Ringkasan singkat

  • bunaken adalah binary Bun (JavaScript) yang menyembunyikan password di kode yang di‑obfuscate dan menggunakan AES‑CBC + gzip untuk menyimpan flag terenkripsi. Tujuan: ekstrak password → turunkan kunci → dekripsi → dekompresi → baca flag. ✅

Artefak & teknik

  • Binary: bunaken (decompile dengan bun-decompile).
  • Enkripsi: AES‑CBC (IV || ciphertext), key = SHA-256(password)[0:16].
  • Obfuscation: string array + RC4‑like decoder → password sulawesi.

Alur penyelesaian (singkat)

  1. Decompile bunaken → temukan decoder string dan panggil c(373, "rG]G") → dapat sulawesi. 🔑
  2. Derive key: SHA256("sulawesi")[0:16].
  3. Dekripsi flag.txt.bunakencrypted (ambil IV 16‑byte pertama), hapus padding, lalu decompress (gzip) → flag. 🔓

Kenapa ini bekerja

  • Semua secret (password) dan logika dekripsi ada di binary (client side) — reverse engineering cukup untuk memulihkan bahan dekripsi. ⚠️

analisis

./bunaken
# ENOENT: no such file or directory, open 'flag.txt'
#     path: "flag.txt",
#  syscall: "open",
#    errno: -2,
#     code: "ENOENT"


# Bun v1.3.6 (Linux x64)

dari error di atas, kita bisa lihat bahwa program mencoba membuka file flag.txt yang tidak ada. Namun, kita punya file flag.txt.bunakencrypted yang kemungkinan besar adalah versi terenkripsi dari flag tersebut.

selain itu kita juga mendapatkan informasi penting bahwa binary ini dibuat menggunakan Bun v1.3.6, yang merupakan runtime JavaScript/TypeScript yang relatif baru. Ini menunjukkan bahwa binary ini mungkin menggunakan fitur-fitur modern dari JavaScript, dan kita mungkin perlu menggunakan teknik reverse engineering yang sesuai untuk memahami bagaimana file flag.txt dihasilkan dari flag.txt.bunakencrypted.

saya mencari informasi di google terkait bun decompile

1771215025947

setelah dibuka kita dapat informasi cara melakukan installasi dan penggunaan bun decompile

1771215133298

Installasi bun decompile

install bun terlebih dahulu jika belum ada

curl -fsSL https://bun.com/install | bash
exec /usr/bin/zsh
bun --version

setelah itu lakukan installasi library bun-decompile

bun add -g @shepherdjerred/bun-decompile

1771215308415

langkah penyelesaian

1, decompile binary bunaken

kita lakukan decompile terhadap binary bunaken untuk mendapatkan source code JavaScript/TypeScript yang digunakan untuk membuat binary tersebut. dengan perintah berikut:

bun-decompile ./bunaken -o ./extracted

1771215396651

2. analisis source code hasil decompile

setelah proses decompile selesai, kita akan mendapatkan folder extracted yang berisi file-file JavaScript/TypeScript hasil decompile. kita buka file utama yang kemungkinan besar berisi logika untuk membuka file flag.txt dan menghasilkan flag tersebut. kita cari di dalam source code untuk menemukan bagian yang mencoba membuka flag.txt dan melihat bagaimana file flag.txt.bunakencrypted digunakan dalam proses tersebut.

bunaken.js

// @bun
function w(){let n=["WR0tF8oezmkl","toString","W603xSol","1tlHJnY","1209923ghGtmw","text","13820KCwBPf","byteOffset","40xRjnfn","Cfa9","bNaXh8oEW6OiW5FcIq","alues","lXNdTmoAgqS0pG","D18RtemLWQhcLConW5a","nCknW4vfbtX+","WOZcIKj+WONdMq","FCk1cCk2W7FcM8kdW4y","a8oNWOjkW551fSk2sZVcNa","yqlcTSo9xXNcIY9vW7dcS8ky","from","iSoTxCoMW6/dMSkXW7PSW4xdHaC","c0ZcS2NdK37cM8o+mW","377886jVoqYx","417805ESwrVS","7197AxJyfv","cu7cTX/cMGtdJSowmSk4W5NdVCkl","W7uTCqXDf0ddI8kEFW","write","encrypt","ted","xHxdQ0m","byteLength","6CCilXQ","304OpHfOi","set","263564pSWjjv","subtle","945765JHdYMe","SHA-256","Bu7dQfxcU3K","getRandomV"];return w=function(){return n},w()}function l(n,r){return n=n-367,w()[n]}var y=l,s=c;function c(n,r){n=n-367;let t=w(),x=t[n];if(c.uRqEit===void 0){var b=function(i){let f="",a="";for(let d=0,o,e,p=0;e=i.charAt(p++);~e&&(o=d%4?o*64+e:e,d++%4)?f+=String.fromCharCode(255&o>>(-2*d&6)):0)e="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=".indexOf(e);for(let d=0,o=f.length;d<o;d++)a+="%"+("00"+f.charCodeAt(d).toString(16)).slice(-2);return decodeURIComponent(a)};let U=function(i,B){let f=[],a=0,d,o="";i=b(i);let e;for(e=0;e<256;e++)f[e]=e;for(e=0;e<256;e++)a=(a+f[e]+B.charCodeAt(e%B.length))%256,d=f[e],f[e]=f[a],f[a]=d;e=0,a=0;for(let p=0;p<i.length;p++)e=(e+1)%256,a=(a+f[e])%256,d=f[e],f[e]=f[a],f[a]=d,o+=String.fromCharCode(i.charCodeAt(p)^f[(f[e]+f[a])%256]);return o};c.yUvSwA=U,c.MmZTqk={},c.uRqEit=!0}let u=t[0],I=n+u,A=c.MmZTqk[I];return!A?(c.ftPoNg===void 0&&(c.ftPoNg=!0),x=c.yUvSwA(x,r),c.MmZTqk[I]=x):x=A,x}(function(n,r){let t=c,x=l,b=n();while(!0)try{if(parseInt(x(405))/1*(parseInt(x(383))/2)+-parseInt(x(385))/3*(parseInt(t(382,"9Dnx"))/4)+parseInt(x(384))/5*(-parseInt(x(393))/6)+parseInt(x(396))/7*(parseInt(x(369))/8)+parseInt(t(381,"R69F"))/9+-parseInt(x(367))/10+-parseInt(x(406))/11===r)break;else b.push(b.shift())}catch(u){b.push(b.shift())}})(w,105028);var h=async(n)=>{let r=l,t=c,x=n instanceof ArrayBuffer?new Uint8Array(n):new Uint8Array(n[t(400,"I2yl")],n[r(368)],n.byteLength);if(x.byteLength===16||x.byteLength===24||x.byteLength===32)return x;let b=await crypto.subtle[t(402,"Fw]1")](r(399),x);return new Uint8Array(b).subarray(0,16)},g=(n,r)=>{let t=l,x=new Uint8Array(n.byteLength+r.byteLength);return x.set(n,0),x[t(395)](r,n[t(392)]),x},m=async(n,r)=>{let t=c,x=l,b=crypto[x(401)+x(372)](new Uint8Array(16)),u=await h(n),I=await crypto[x(397)][t(371,"kAmA")](t(370,"CYgn"),u,{name:"AES-CBC"},!1,[x(389)]),A=await crypto.subtle[x(389)]({name:t(375,"dHTh"),iv:b},I,r);return g(b,new Uint8Array(A))},S=Bun[s(391,"9Dnx")](s(377,"R69F")),k=await S[y(407)](),v=await Bun[s(387,"f]pG")+"ss"](k),z=await m(Buffer[y(380)](s(373,"rG]G")),v);Bun[y(388)]("flag.txt.b"+s(374,"CYgn")+y(390),Buffer[s(404,"(Y*]")](z)[y(403)](s(376,"$lpa")));

//# debugId=89BBB7E67C06C2CD64756E2164756E21

3. decrypt password

Penjelasan singkat:

  • Fungsi c(index, key) pada hasil decompile adalah string-obfuscation/decoder — ia pertama‑tama base64‑decode data internal, lalu menjalankan RC4‑like XOR dengan key (argumen kedua). Untuk mendapatkan password kita cukup mengeksekusi c(373, "rG]G").

Langkah teknis (cara cepat): buat file decrypt_password.js berisi fungsi yang di‑decompile (fungsi w + c) dan jalankan dengan Node.

decrypt_password.js

// 1. Array String & Accessor 'w'
function w() {
  let n = [
    "WR0tF8oezmkl",
    "toString",
    "W603xSol",
    "1tlHJnY",
    "1209923ghGtmw",
    "text",
    "13820KCwBPf",
    "byteOffset",
    "40xRjnfn",
    "Cfa9",
    "bNaXh8oEW6OiW5FcIq",
    "alues",
    "lXNdTmoAgqS0pG",
    "D18RtemLWQhcLConW5a",
    "nCknW4vfbtX+",
    "WOZcIKj+WONdMq",
    "FCk1cCk2W7FcM8kdW4y",
    "a8oNWOjkW551fSk2sZVcNa",
    "yqlcTSo9xXNcIY9vW7dcS8ky",
    "from",
    "iSoTxCoMW6/dMSkXW7PSW4xdHaC",
    "c0ZcS2NdK37cM8o+mW",
    "377886jVoqYx",
    "417805ESwrVS",
    "7197AxJyfv",
    "cu7cTX/cMGtdJSowmSk4W5NdVCkl",
    "W7uTCqXDf0ddI8kEFW",
    "write",
    "encrypt",
    "ted",
    "xHxdQ0m",
    "byteLength",
    "6CCilXQ",
    "304OpHfOi",
    "set",
    "263564pSWjjv",
    "subtle",
    "945765JHdYMe",
    "SHA-256",
    "Bu7dQfxcU3K",
    "getRandomV",
  ];
  return (
    (w = function () {
      return n;
    }),
    w()
  );
}

// 2. Simple Accessor 'l' (digunakan saat shuffle untuk baca angka)
function l(n, r) {
  return ((n = n - 367), w()[n]);
}

// 3. Complex Decryptor 'c'
var c = function (n, r) {
  n = n - 367;
  let t = w(),
    x = t[n];
  if (c.uRqEit === void 0) {
    var b = function (i) {
      let f = "",
        a = "";
      for (
        let d = 0, o, e, p = 0;
        (e = i.charAt(p++));
        ~e && ((o = d % 4 ? o * 64 + e : e), d++ % 4) ? (f += String.fromCharCode(255 & (o >> ((-2 * d) & 6)))) : 0
      )
        e = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=".indexOf(e);
      for (let d = 0, o = f.length; d < o; d++) a += "%" + ("00" + f.charCodeAt(d).toString(16)).slice(-2);
      return decodeURIComponent(a);
    };
    let U = function (i, B) {
      let f = [],
        a = 0,
        d,
        o = "";
      i = b(i);
      let e;
      for (e = 0; e < 256; e++) f[e] = e;
      for (e = 0; e < 256; e++) ((a = (a + f[e] + B.charCodeAt(e % B.length)) % 256), (d = f[e]), (f[e] = f[a]), (f[a] = d));
      ((e = 0), (a = 0));
      for (let p = 0; p < i.length; p++)
        ((e = (e + 1) % 256),
          (a = (a + f[e]) % 256),
          (d = f[e]),
          (f[e] = f[a]),
          (f[a] = d),
          (o += String.fromCharCode(i.charCodeAt(p) ^ f[(f[e] + f[a]) % 256])));
      return o;
    };
    ((c.yUvSwA = U), (c.MmZTqk = {}), (c.uRqEit = !0));
  }
  let u = t[0],
    I = n + u,
    A = c.MmZTqk[I];
  return (!A ? (c.ftPoNg === void 0 && (c.ftPoNg = !0), (x = c.yUvSwA(x, r)), (c.MmZTqk[I] = x)) : (x = A), x);
};

// 4. Shuffle Logic (Bagian IIFE dari bunaken.js)
(function (n, r) {
  let t = c,
    x = l,
    b = n();
  while (true) {
    try {
      if (
        (parseInt(x(405)) / 1) * (parseInt(x(383)) / 2) +
          (-parseInt(x(385)) / 3) * (parseInt(t(382, "9Dnx")) / 4) +
          (parseInt(x(384)) / 5) * (-parseInt(x(393)) / 6) +
          (parseInt(x(396)) / 7) * (parseInt(x(369)) / 8) +
          parseInt(t(381, "R69F")) / 9 +
          -parseInt(x(367)) / 10 +
          -parseInt(x(406)) / 11 ===
        r
      )
        break;
      else b.push(b.shift());
    } catch (u) {
      b.push(b.shift());
    }
  }
})(w, 105028);

// 5. Success! Get the password.
try {
  const password = c(373, "rG]G");
  console.log("Decrypted Password:", password);
} catch (e) {
  console.error(e);
}

Jalankan:

node decrypt_password.js

Output:

Decrypted Password: sulawesi

1771215972413

4. decrypt file terenkripsi (flag.txt.bunakencrypted)

Observasi dari source decompiled:

  • Binary menggunakan AES‑CBC.
  • Kunci AES = SHA‑256(password) lalu truncate ke 16 byte (AES‑128 key).
  • File terenkripsi disimpan sebagai IV || ciphertext (IV 16 byte di depan).

decrypt_flag.py

import base64
import hashlib
import gzip
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

def decrypt_flag_from_file(filename, password):
    enc = open(filename, "rb").read().strip()

    # decode base64
    data = base64.b64decode(enc)

    iv = data[:16]
    ct = data[16:]

    key = hashlib.sha256(password.encode()).digest()[:16]

    print("[*] Key:", key.hex())
    print("[*] IV :", iv.hex())

    cipher = AES.new(key, AES.MODE_CBC, iv)
    decrypted = cipher.decrypt(ct)

    try:
        decrypted = unpad(decrypted, 16)
    except:
        print("[!] No padding or bad padding")

    print("[*] Raw decrypted:", decrypted)
    print("[*] Hex:", decrypted.hex())

    # cek apakah gzip
    if decrypted[:2] == b'\x1f\x8b':
        print("[*] GZIP detected, decompressing...")
        decrypted = gzip.decompress(decrypted)

    print("\n[+] FLAG:", decrypted.decode(errors="ignore"))

decrypt_flag_from_file("flag.txt.bunakencrypted", "sulawesi")

Install dependency (jika belum):

pip install pycryptodome

setelah itu jalankan file decrypt nya.

python decrypt_flag.py

1771216631970

flag

C2C{BUN_AwKward_ENcryption_compression_obfuscation}

On this page