I’ll be sharing some solutions to the challenges I solved at TUCTF 2023
Bludgeon the booty
This challenge involved connecting to a server and rotating a combination lock.
Because of the constant connection issues during the CTF, I found that building a new connection every time was more reliable (a new combination is generated on connect).
You may need to restart this program multiple times.
const net = require("net");
class Semaphore {
/**
* Creates a semaphore that limits the number of concurrent Promises being handled
* @param {*} maxConcurrentRequests max number of concurrent promises being handled at any time
*/
constructor(maxConcurrentRequests = 1) {
this.currentRequests = [];
this.runningRequests = 0;
this.maxConcurrentRequests = maxConcurrentRequests;
}
/**
* Returns a Promise that will eventually return the result of the function passed in
* Use this to limit the number of concurrent function executions
* @param {*} fnToCall function that has a cap on the number of concurrent executions
* @param {...any} args any arguments to be passed to fnToCall
* @returns Promise that will resolve with the resolved value as if the function passed in was directly called
*/
callFunction(fnToCall, ...args) {
return new Promise(async (resolve, reject) => {
this.currentRequests.push({
resolve,
reject,
fnToCall,
args,
});
await this.tryNext();
});
}
async tryNext() {
if (!this.currentRequests.length) {
return;
} else if (this.runningRequests < this.maxConcurrentRequests) {
let { resolve, reject, fnToCall, args } =
this.currentRequests.shift();
this.runningRequests++;
let req = fnToCall(...args);
req.then((res) => resolve(res))
.catch((err) => reject(err))
.finally(async () => {
this.runningRequests--;
await this.tryNext();
});
}
}
}
function clientele(numbero) {
return new Promise((resolve) => {
const client = new net.Socket();
// nc chal.tuctf.com 30002
client.connect(30002, "chal.tuctf.com", function () {
console.log("Connected");
});
function rotateWheel(id) {
console.log("Rotated wheel " + id);
client.write("1\n");
client.write(id + "\n");
client.write("+\n");
// Rotated wheel
}
function initializeTo(w1, w2, w3, w4) {
// Rotate wheel from 0 positions to w1, w2, w3, w4
for (let i = 0; i < w1; i++) {
rotateWheel(1);
}
for (let i = 0; i < w2; i++) {
rotateWheel(2);
}
for (let i = 0; i < w3; i++) {
rotateWheel(3);
}
for (let i = 0; i < w4; i++) {
rotateWheel(4);
}
}
let initial = true;
client.on("data", function (data) {
if (initial) {
initial = false;
initializeTo(
numbero % 10,
Math.floor(numbero / 10) % 10,
Math.floor(numbero / 100) % 10,
Math.floor(numbero / 1000) % 10,
);
resolve();
}
if (data.toString().match(/TUCTF/)) {
console.log(data.toString());
resolve(data.toString());
client.destroy();
}
});
});
}
async function main() {
let smp = new Semaphore(10);
for (let i = 9999; i > 0; i--) {
await smp.callFunction(clientele, i);
}
}
main();
Hacker-Typer
I solved this challenge using the browser console:
function handleNext(obj) {
console.log(obj);
fetch("https://hacker-typer.tuctf.com/check_word", {
credentials: "include",
headers: {
Accept: "*/*",
"Content-Type": "application/x-www-form-urlencoded",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"Sec-GPC": "1",
},
referrer: "https://hacker-typer.tuctf.com/",
body: "word=" + encodeURIComponent(obj["next_word"]),
method: "POST",
mode: "cors",
})
.then((res) => res.json())
.then((onj) => {
setTimeout(() => handleNext(onj), 1);
});
}
handleNext({ next_word: "ENTER_FIRST_WORD_HERE" });