I literally spent about 6 hours on this challenge sob and eventually solved it with my teammates.

image.png

0x00 Basic Informations

❯ file ./chal
./chal: 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]=b0ba6747a1f840228cb23022a02690d4b8d1f470, not stripped

first run:

(base) sisubeny@ubuntu:/Users/sisubeny/ctf/puctf/bun$ ./chal
warn: CPU lacks AVX support, strange crashes may occur. Reinstall Bun or use *-baseline build:
  <https://github.com/oven-sh/bun/releases/download/bun-v1.3.8/bun-linux-x64-baseline.zip>
error: Cannot find module '@std/crypto/crypto' from '/$bunfs/root/index'

Bun v1.3.8 (Linux x64)

as far as we know, this is a bun executable binary, so we can use bun-decompile .

bun add -g @shepherdjerred/bun-decompile
bun-decompile ./chal -o ./extracted

to extract its source code.

0x01 Extracted

├── bun.lock
├── chal
├── decompiled
│   ├── node_modules
│   ├── bundled
│   │   └── index.js
│   └── metadata.json
└── package.json

index.js:

Notice that the admin PIN is derived from the service startup time rounded down to a 5-second bucket and the PIN is inserted into SQLite as BigInt(time) ** 2n, which gets truncated to a signed 64-bit integer.

var db = new Database(":memory:");
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, username STRING, pin INTEGER, admin BOOLEAN)");

var time = Date.now();
time -= time % 5000;

var insertUser = db.prepare("INSERT INTO users (username, pin, admin) VALUES (?, ?, ?)");
insertUser.run("admin", BigInt(time) ** 2n, true);

Login compares against the stored SQLite integer:

async function checkLogin(username, pin) {
  const getUser = db.prepare("SELECT * FROM users WHERE username = ?");
  const user = getUser.get(username);
  if (!user) return false;
  return user.pin === pin;
}

Once we get admin primitive we can install a controllable Bun package:

const install = Bun.spawnSync({
  cmd: ["bun", "add", "--no-save", "--no-cache", message.package],
  stdout: "pipe",
  stderr: "pipe"
});