Hitcon
pins
Challenge Description:
4 digits pin are too weak… How about 100 of them?
100pins.js
!/usr/bin/env node
const rl = require("readline").createInterface({
input: process.stdin,
output: process.stdout,
});
rl.on("close", () => {
yellow("Bye");
process.exit(1);
});
const ask = (q) => new Promise((resolve) => rl.question(q, (s) => resolve(s)));
const colorLog = (c) => (...args) =>
console.log(`\x1b[1;${c}m` + args.join(" ") + "\x1b[0m");
const red = colorLog(31);
const green = colorLog(32);
const yellow = colorLog(33);
const info = colorLog(0);
const flag = process.env["FLAG"];
if (flag === undefined || !flag.startsWith("hitcon")) {
red("Oops... missing flag, please contact admin");
process.exit(2);
}
setTimeout(() => {
red("Too sloooooooooooow :(");
process.exit(3);
}, 180 * 1000);
const N = 100;
const candidates = Array.from({ length: 10000 }, (_, i) =>
i.toString().padStart(4, "0")
).filter((x) => new Set(x).size === 4);
const pins = Array.from(
{ length: N },
() => candidates[Math.floor(Math.random() * candidates.length)]
);
const match = (pin, s) => {
let a = 0;
let b = 0;
for (let i = 0; i < s.length; i++) {
const j = pin.indexOf(s[i]);
if (j === i) a++;
else if (j !== -1) b++;
}
return [a, b];
};
const sha256 = (s) =>
require("crypto").createHash("sha256").update(s).digest("hex");
const pow = async () => {
const nonce = Math.random().toString(36).slice(-10);
const s = await ask(`Show me sha256("${nonce}" + s) ends with "00000": `);
const hash = sha256(nonce + s);
if (!hash.endsWith("00000")) {
red("Huh?");
process.exit(4);
}
};
const main = async () => {
green(`Welcome to FLAG locker ${process.version}`);
await pow();
info(`=== FLAG is locked with ${N} pins ===`);
let rem = 128;
for (let i = 0; i < N; i++) {
if (--rem < 0) {
red("Too many errors! Device is wiped and the flag is gone ¯\\_(ツ)_/¯");
process.exit(5);
}
const pin = pins[i];
const s = await ask(`Pin ${i + 1}? `);
const [a, b] = match(pin, s);
if (a === 4) {
green("OK");
continue;
}
yellow(`Hmm... hint: ${a}A${b}B`);
i--;
}
green("FLAG Unlocked:", flag);
process.exit(0);
};
main().finally(() => process.exit(6));
So program gives a random string and asks for a salt that, when put together and used sha256 on, creates a string that ends in ‘00000’. So I just need to figure out how to produce a hash that ends in ‘00000’. One common vulnerability of sha256 is a preimage attack. A preimage attack is when you try to find a message that has a specific hash value. Since I know the value ending that I need (‘00000’), I think I need to implement this attack. The main issue is that I’m not entirely sure how to implement this attack to get the salt that would be used to get the correct ending.
Update
Based on writeup: https://www.josephsurin.me/posts/2020-11-30-hitcon-ctf-2020-100-pins-writeup
Based on the writeup, since we know that the server is asking us to guess 100 four digit pins with 128 attempts, without any repeating digits, there are 5040 possible pins. The script uses “Math.random()”, which apparently isn’t cryptographically secure, using Xorshift128+. Using this knowledge and the hints given, according to the writeup, narrowing down the possibilities to those with the highest entropy would give the solution to move to the next part of the problem. Leaking the outputs produced by the random function involves reversing the Xorshift128+. Combining all of this would give the flag. So it turns out that a preimage attack wasn’t needed at all, just an exploitation of a PRNG.
another secret note
Challenge Description:
Can you read the secret?
prob.py
’’’ #!/usr/bin/env node
const rl = require(“readline”).createInterface({ input: process.stdin, output: process.stdout, }); rl.on(“close”, () => { yellow(“Bye”); process.exit(1); });
const ask = (q) => new Promise((resolve) => rl.question(q, (s) => resolve(s)));
const colorLog = (c) => (…args) =>
console.log(\x1b[1;${c}m
+ args.join(“ “) + “\x1b[0m”);
const red = colorLog(31);
const green = colorLog(32);
const yellow = colorLog(33);
const info = colorLog(0);
const flag = process.env[“FLAG”]; if (flag === undefined || !flag.startsWith(“hitcon”)) { red(“Oops… missing flag, please contact admin”); process.exit(2); }
setTimeout(() => { red(“Too sloooooooooooow :(“); process.exit(3); }, 180 * 1000);
const N = 100; const candidates = Array.from({ length: 10000 }, (_, i) => i.toString().padStart(4, “0”) ).filter((x) => new Set(x).size === 4); const pins = Array.from( { length: N }, () => candidates[Math.floor(Math.random() * candidates.length)] );
const match = (pin, s) => { let a = 0; let b = 0; for (let i = 0; i < s.length; i++) { const j = pin.indexOf(s[i]); if (j === i) a++; else if (j !== -1) b++; } return [a, b]; };
const sha256 = (s) => require(“crypto”).createHash(“sha256”).update(s).digest(“hex”);
const pow = async () => {
const nonce = Math.random().toString(36).slice(-10);
const s = await ask(Show me sha256("${nonce}" + s) ends with "00000":
);
const hash = sha256(nonce + s);
if (!hash.endsWith(“00000”)) {
red(“Huh?”);
process.exit(4);
}
};
const main = async () => {
green(Welcome to FLAG locker ${process.version}
);
await pow();
info(=== FLAG is locked with ${N} pins ===
);
let rem = 128;
for (let i = 0; i < N; i++) {
if (–rem < 0) {
red(“Too many errors! Device is wiped and the flag is gone ¯\(ツ)/¯”);
process.exit(5);
}
const pin = pins[i];
const s = await ask(`Pin ${i + 1}? `);
const [a, b] = match(pin, s);
if (a === 4) {
green("OK");
continue;
}
yellow(`Hmm... hint: ${a}A${b}B`);
i--; }
green(“FLAG Unlocked:”, flag); process.exit(0); };
main().finally(() => process.exit(6)); ‘’’
In this challenge, we come across the same problem as in the pins challenge. I need to figure out what the salt is to get a certain hash value. I believe this would also use a preimage attack. I tried decrypting the given hash value, but I’m still unsure of how to get the salt.
Update
Based on writeup: https://ctftime.org/writeup/25119
According to the writeup, the first half of the flag is recoverable using an oracle attack, and the second half is recovered by sending a ciphertext to the login function. First we have to ensure the padding is correct, based on knowing the schema of the flag. Then, we’d input the first block of ciphertext as the IV, and then the second block of ciphertext alone. After trying this method of inputting into the AES-CBC algorithm, we obtain “hitcon{JSON_is_5”, the first half of the string. To get the second half of the flag, we need to switch around the way we use the ciphertext to input into the IV in a way that’s best explained in the writeup link above. Doing this ultimately removes the need for a preimage attack and gives the flag for the challenge.