I participated DiceCTF last week, it was quite fun! Didn’t expect it was this difficult..
Here is some of the challenge writeups I think quite special:
Challenges
knock-knock
Description
Files
This challenge is quite special, it maybe appeared in real application, it show us how a simple mistake can lead to serious bug
First goto the link provided https://knock-knock.mc.ax/:
Then tried to create a paste:
Everything seems fine, check the source code:
const crypto = require('crypto');
class Database {
constructor() {
this.notes = [];
this.secret = `secret-${crypto.randomUUID}`;
}
createNote({ data }) {
const id = this.notes.length;
this.notes.push(data);
return {
id,
token: this.generateToken(id),
};
}
getNote({ id, token }) {
if (token !== this.generateToken(id)) return { error: 'invalid token' };
if (id >= this.notes.length) return { error: 'note not found' };
return { data: this.notes[id] };
}
generateToken(id) {
return crypto
.createHmac('sha256', this.secret)
.update(id.toString())
.digest('hex');
}
}
const db = new Database();
db.createNote({ data: process.env.FLAG });
const express = require('express');
const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(express.static('public'));
app.post('/create', (req, res) => {
const data = req.body.data ?? 'no data provided.';
const { id, token } = db.createNote({ data: data.toString() });
res.redirect(`/note?id=${id}&token=${token}`);
});
app.get('/note', (req, res) => {
const { id, token } = req.query;
const note = db.getNote({
id: parseInt(id ?? '-1'),
token: (token ?? '').toString(),
});
if (note.error) {
res.send(note.error);
} else {
res.send(note.data);
}
});
app.listen(3000, () => {
console.log('listening on port 3000');
});
After checking the source code, we can see the flag is put as the first note (id=0)
But how are we able to get the first note without the token
?
Because the token is generated with HMAC with random string: crypto.randomUUID
Trying Prototype pollution
Then I stucked for few hours trying to exploit prototype pollution like this writeup but failed 😂
Build in docker
Eventually I ran up of ideas, so I decided to build this inside docker and investigate more
Before build it we need to run install the libraries:
Run npm init
:
npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help init` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (knock)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /home/hong/ctf/dicectf/knock/package.json:
{
"name": "knock",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Is this OK? (yes)
Then run npm install express
:
npm install express
added 50 packages, and audited 51 packages in 2s
2 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
After that, we can build it by docker build -t knock .
, then run it using docker -d --name knock --rm -p 8080:3000 knock
Solving
After some experiment, notice every time I restart the token
value is the same!!
That means we can predict the token
when id
=0 and get the flag!!
Then I get the token in the docker container:
docker exec -it knock bash
node@a0343b6e5f5c:/app$ ls
Dockerfile index.js node_modules package-lock.json package.json yarn.lock
node@a0343b6e5f5c:/app$ node
Welcome to Node.js v17.4.0.
Type ".help" for more information.
> class Database {
... constructor() {
..... this.notes = [];
..... this.secret = `secret-${crypto.randomUUID}`;
..... }
...
... createNote({ data }) {
... const id = this.notes.length;
... this.notes.push(data);
... return {
..... id,
..... token: this.generateToken(id),
..... };
... }
...
... getNote({ id, token }) {
... if (token !== this.generateToken(id)) return { error: 'invalid token' };
... if (id >= this.notes.length) return { error: 'note not found' };
... return { data: this.notes[id] };
... }
...
... generateToken(id) {
... return crypto
... .createHmac('sha256', this.secret)
... .update(id.toString())
... .digest('hex');
... }
... }
undefined
>
> const db = new Database();
undefined
> db.generateToken(0);
'7bd881fe5b4dcc6cdafc3e86b4a70e07cfd12b821e09a81b976d451282f6e264'
Then goto https://knock-knock.mc.ax/note?id=0&token=7bd881fe5b4dcc6cdafc3e86b4a70e07cfd12b821e09a81b976d451282f6e264
and get the freaking flag!!
Flag
dice{1_d00r_y0u_d00r_w3_a11_d00r_f0r_1_d00r}
After the competition notice the crypto.randomUUID
in secret
is not called, therefore it is static everytime:
db.secret
'secret-function randomUUID(options) {\n' +
' if (options !== undefined)\n' +
" validateObject(options, 'options');\n" +
' const {\n' +
' disableEntropyCache = false,\n' +
' } = options || {};\n' +
'\n' +
" validateBoolean(disableEntropyCache, 'options.disableEntropyCache');\n" +
'\n' +
' return disableEntropyCache ? getUnbufferedUUID() : getBufferedUUID();\n' +
'}'
flagle
Description
Goto the link given https://flagle.mc.ax/
As you can see, we need to “guess” the flag, each box can type 5 characters:
And we know the flag starts with dice{
, looks like when guess correctly it turns into green:
Lets analyse the source:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Flagle</title>
<link rel="stylesheet" href="style.css"/>
</head>
<body>
<div class="game-container">
<div class="guess" id="guess-1">
<input class="letter" data-guess="1" data-letter="1" maxlength="5" disabled>
<input class="letter" data-guess="1" data-letter="2" maxlength="5" disabled>
<input class="letter" data-guess="1" data-letter="3" maxlength="5" disabled>
<input class="letter" data-guess="1" data-letter="4" maxlength="5" disabled>
<input class="letter" data-guess="1" data-letter="5" maxlength="5" disabled>
<input class="letter" data-guess="1" data-letter="6" maxlength="5" disabled>
</div>
<div class="guess" id="guess-2">
<input class="letter" data-guess="2" data-letter="1" maxlength="5" disabled>
<input class="letter" data-guess="2" data-letter="2" maxlength="5" disabled>
<input class="letter" data-guess="2" data-letter="3" maxlength="5" disabled>
<input class="letter" data-guess="2" data-letter="4" maxlength="5" disabled>
<input class="letter" data-guess="2" data-letter="5" maxlength="5" disabled>
<input class="letter" data-guess="2" data-letter="6" maxlength="5" disabled>
</div>
<div class="guess" id="guess-3">
<input class="letter" data-guess="3" data-letter="1" maxlength="5" disabled>
<input class="letter" data-guess="3" data-letter="2" maxlength="5" disabled>
<input class="letter" data-guess="3" data-letter="3" maxlength="5" disabled>
<input class="letter" data-guess="3" data-letter="4" maxlength="5" disabled>
<input class="letter" data-guess="3" data-letter="5" maxlength="5" disabled>
<input class="letter" data-guess="3" data-letter="6" maxlength="5" disabled>
</div>
<div class="guess" id="guess-4">
<input class="letter" data-guess="4" data-letter="1" maxlength="5" disabled>
<input class="letter" data-guess="4" data-letter="2" maxlength="5" disabled>
<input class="letter" data-guess="4" data-letter="3" maxlength="5" disabled>
<input class="letter" data-guess="4" data-letter="4" maxlength="5" disabled>
<input class="letter" data-guess="4" data-letter="5" maxlength="5" disabled>
<input class="letter" data-guess="4" data-letter="6" maxlength="5" disabled>
</div>
<div class="guess" id="guess-5">
<input class="letter" data-guess="5" data-letter="1" maxlength="5" disabled>
<input class="letter" data-guess="5" data-letter="2" maxlength="5" disabled>
<input class="letter" data-guess="5" data-letter="3" maxlength="5" disabled>
<input class="letter" data-guess="5" data-letter="4" maxlength="5" disabled>
<input class="letter" data-guess="5" data-letter="5" maxlength="5" disabled>
<input class="letter" data-guess="5" data-letter="6" maxlength="5" disabled>
</div>
<div class="guess" id="guess-6">
<input class="letter" data-guess="6" data-letter="1" maxlength="5" disabled>
<input class="letter" data-guess="6" data-letter="2" maxlength="5" disabled>
<input class="letter" data-guess="6" data-letter="3" maxlength="5" disabled>
<input class="letter" data-guess="6" data-letter="4" maxlength="5" disabled>
<input class="letter" data-guess="6" data-letter="5" maxlength="5" disabled>
<input class="letter" data-guess="6" data-letter="6" maxlength="5" disabled>
</div>
<button id="guess-button">GUESS</button>
</div>
<script src="flag-checker.js"></script>
<script src="script.js"></script>
<script>
const guess = Module.cwrap('guess', 'number', ['number', 'string']);
const CORRECT = 0;
const WRONG_LOCATION = 1;
const INCORRECT = 2;
const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&()*+,-./:;<=>?@[]^_{|}~";
const get_input = (guess_idx, letter_idx) => {
return document.querySelector(`[data-guess="${guess_idx}"][data-letter="${letter_idx}"]`)
};
const keydown_listener = (e) => {
const target = e.target;
const guess_idx = +target.dataset.guess;
const letter_idx = +target.dataset.letter;
if (e.modifiers?.length > 0) {
return;
}
if (e.key.length === 1 && !alphabet.includes(e.key)) {
e.preventDefault();
return false;
}
if (e.key === "Backspace" && target.value.length === 0 && letter_idx > 1) {
get_input(guess_idx, letter_idx - 1).focus();
} else if (e.key === "Delete" && target.value.length === 0 && letter_idx < 6) {
const elem = get_input(guess_idx, letter_idx + 1);
elem.focus();
elem.setSelectionRange(0, 0);
e.preventDefault();
return false;
} else if (e.key.length === 1 && target.value.length === 5 && letter_idx < 6) {
get_input(guess_idx, letter_idx + 1).focus();
} else if (e.key === "Enter") {
submit_guess();
}
};
let current_guess = 1;
const guess_button = document.getElementById('guess-button');
const submit_guess = () => {
let correct = 0;
let input_text = '';
for (let i = 1; i <= 6; ++i) {
const el = get_input(current_guess, i);
const guess_val = el.value;
input_text += guess_val;
const result = guess(i, guess_val);
console.log(result, guess_val, i, guess_val);
if (result === CORRECT) {
el.classList.add('correct');
if (current_guess < 6) {
const next = get_input(current_guess + 1, i);
next.value = guess_val;
next.classList.add('correct');
}
correct++;
} else if (result === WRONG_LOCATION) {
el.classList.add('partial');
} else if (result === INCORRECT) {
el.classList.add('incorrect');
}
el.disabled = true;
el.removeEventListener('keydown', keydown_listener);
}
current_guess++;
if (correct === 6) {
prompt('Congrats! Here\'s your flag:', input_text);
}
if (current_guess > 6) {
guess_button.disabled = true;
} else {
init_guess();
}
};
guess_button.onclick = submit_guess;
const init_guess = () => {
for (let i = 1; i <= 6; ++i) {
const el = get_input(current_guess, i);
if (!el.classList.contains('correct'))
el.disabled = false;
el.addEventListener('keydown', keydown_listener);
}
};
init_guess();
</script>
</body>
</html>
The main code in the submit_guess
function:
const submit_guess = () => {
let correct = 0;
let input_text = '';
for (let i = 1; i <= 6; ++i) {
const el = get_input(current_guess, i);
const guess_val = el.value;
input_text += guess_val;
const result = guess(i, guess_val);
console.log(result, guess_val, i, guess_val);
if (result === CORRECT) {
el.classList.add('correct');
if (current_guess < 6) {
const next = get_input(current_guess + 1, i);
next.value = guess_val;
next.classList.add('correct');
}
correct++;
} else if (result === WRONG_LOCATION) {
el.classList.add('partial');
} else if (result === INCORRECT) {
el.classList.add('incorrect');
}
el.disabled = true;
el.removeEventListener('keydown', keydown_listener);
}
current_guess++;
if (correct === 6) {
prompt('Congrats! Here\'s your flag:', input_text);
}
if (current_guess > 6) {
guess_button.disabled = true;
} else {
init_guess();
}
};
As you can see, our input is pass into function guess
, then it checks the return value equals CORRECT
If “guess” all correctly 6 times we got the flag
Decompile WASM file
When viewing the source we saw a flag-check.wasm file (WebAssembly file)
It is difficult to read, so we need to decompile it first
I used this tool from github: https://github.com/WebAssembly/wabt
Download the file, then run wasm-decompile flag-checker.wasm -o flag-check.c
this will decompile the WASM code to C code
Checking the decompiled code you can see the guess
function in here:
export function guess(a:int, b:int):int {
var c:int = g_a - 16;
g_a = c;
var d:int = 2;
if (f_k(b) != 5) goto B_a;
if (eqz(streq(b, 1024))) goto B_b;
d = a != 1;
goto B_a;
label B_b:
var e:int = b[4]:ubyte;
d = b[3]:ubyte;
var f:int = b[2]:ubyte;
var g:int = b[1]:ubyte;
c[11]:byte = b[0]:ubyte;
c[10]:byte = g;
c[9]:byte = f;
c[8]:byte = d;
d = c[10]:ubyte;
c[10]:byte = c[9]:ubyte;
c[9]:byte = d;
d = c[9]:ubyte;
c[9]:byte = c[8]:ubyte;
c[8]:byte = d;
d = c[9]:ubyte;
c[9]:byte = c[11]:ubyte;
c[11]:byte = d;
d = c[9]:ubyte;
c[9]:byte = c[8]:ubyte;
c[8]:byte = d;
d = c[11]:ubyte;
c[11]:byte = c[10]:ubyte;
c[10]:byte = d;
if (c[11]:ubyte != 51) goto B_c;
if (c[10]:ubyte != 108) goto B_c;
if (c[9]:ubyte != 33) goto B_c;
d = c[8]:ubyte;
if ((e & 255) != 68) goto B_c;
if ((d & 255) != 70) goto B_c;
d = a != 2;
goto B_a;
label B_c:
f = b[1]:byte;
if (f * (d = b[0]:byte) != 4800) goto B_d;
g = b[2]:byte;
if (g + d != 178) goto B_d;
if (g + f != 126) goto B_d;
if (g * (d = b[3]:byte) != 9126) goto B_d;
if (d - (f = b[4]:byte) != 62) goto B_d;
if (g * 4800 - f * d != 367965) goto B_d;
d = a != 3;
goto B_a;
label B_d:
if (eqz(env_validate_4(b))) goto B_e;
d = a != 4;
goto B_a;
label B_e:
var h:int = b[4]:ubyte;
g = b[3]:byte;
e = b[2]:byte;
f = b[1]:byte;
c[15]:byte = (b = b[0]:byte);
c[14]:byte = f;
c[13]:byte = e;
c[12]:byte = g;
c[15]:byte = c[15]:ubyte + 12;
c[14]:byte = c[14]:ubyte + 4;
c[13]:byte = c[13]:ubyte + 6;
c[12]:byte = c[12]:ubyte + 2;
if (c[15]:ubyte != 121) goto B_f;
if (c[14]:ubyte != 68) goto B_f;
if (c[13]:ubyte != 126) goto B_f;
d = c[12]:ubyte;
if ((h & 255) != 77) goto B_f;
if ((d & 255) != 35) goto B_f;
d = a != 5;
goto B_a;
label B_f:
d = 2;
if ((f + 2933) * (b + 1763) != 5483743) goto B_a;
if ((h & 255) != 125) goto B_a;
if ((g + 1546) * (e + 3913) != 6431119) goto B_a;
d = a != 6;
label B_a:
g_a = c + 16;
return d;
}
Guess the flag
The guess
function is abit hard to read, luckily it provided in other function like the first validation in validate_1
:
export function validate_1(a:int):int {
return streq(a, 1024)
}
streq should be string equal I guess?
It compare the our input to 1024?
Nope, it refer to an address in the code here:
data d_a(offset: 1024) = "dice{\00";
Therefore the first validation is comparing the flag format dice{
Second
Second validation abit compilcated:
function validate(a:int, b:int, c:int, d:int, e:int):int {
var f:int = g_a - 16;
f[15]:byte = a;
f[14]:byte = b;
f[13]:byte = c;
f[12]:byte = d;
d = f[14]:ubyte;
f[14]:byte = f[13]:ubyte;
f[13]:byte = d;
//02134
d = f[13]:ubyte;
f[13]:byte = f[12]:ubyte;
f[12]:byte = d;
//02314
d = f[13]:ubyte;
f[13]:byte = f[15]:ubyte;
f[15]:byte = d;
//32014
d = f[13]:ubyte;
f[13]:byte = f[12]:ubyte;
f[12]:byte = d;
//32104
d = f[15]:ubyte;
f[15]:byte = f[14]:ubyte;
f[14]:byte = d;
//23104
d = 0;
if (f[15]:ubyte != 51) goto B_a;
if (f[14]:ubyte != 108) goto B_a;
if (f[13]:ubyte != 33) goto B_a;
d = e == 68 & f[12]:ubyte == 70;
label B_a:
return d;
}
It just wrap our input into different position and compare it with (51,108,33,70,68) which is ASCII
But I failed to reverse the position of the input, so I decided to brute force it…
First I use python to generate all possible position of the 5 characters:
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> bytearray([51,108,33,70,68])
bytearray(b'3l!FD')
>>> from itertools import permutations
>>> for l in list(permutations('3l!FD')):
... print('"'+''.join(l)+'"',end=',')
...
"3l!FD","3l!DF","3lF!D","3lFD!","3lD!F","3lDF!","3!lFD","3!lDF","3!FlD","3!FDl","3!DlF","3!DFl","3Fl!D","3FlD!","3F!lD","3F!Dl","3FDl!","3FD!l","3Dl!F","3DlF!","3D!lF","3D!Fl","3DFl!","3DF!l","l3!FD","l3!DF","l3F!D","l3FD!","l3D!F","l3DF!","l!3FD","l!3DF","l!F3D","l!FD3","l!D3F","l!DF3","lF3!D","lF3D!","lF!3D","lF!D3","lFD3!","lFD!3","lD3!F","lD3F!","lD!3F","lD!F3","lDF3!","lDF!3","!3lFD","!3lDF","!3FlD","!3FDl","!3DlF","!3DFl","!l3FD","!l3DF","!lF3D","!lFD3","!lD3F","!lDF3","!F3lD","!F3Dl","!Fl3D","!FlD3","!FD3l","!FDl3","!D3lF","!D3Fl","!Dl3F","!DlF3","!DF3l","!DFl3","F3l!D","F3lD!","F3!lD","F3!Dl","F3Dl!","F3D!l","Fl3!D","Fl3D!","Fl!3D","Fl!D3","FlD3!","FlD!3","F!3lD","F!3Dl","F!l3D","F!lD3","F!D3l","F!Dl3","FD3l!","FD3!l","FDl3!","FDl!3","FD!3l","FD!l3","D3l!F","D3lF!","D3!lF","D3!Fl","D3Fl!","D3F!l","Dl3!F","Dl3F!","Dl!3F","Dl!F3","DlF3!","DlF!3","D!3lF","D!3Fl","D!l3F","D!lF3","D!F3l","D!Fl3","DF3l!","DF3!l","DFl3!","DFl!3","DF!3l","DF!l3",>>>
Then brute force it in inspect console tab:
var inp = ["3l!FD","3l!DF","3lF!D","3lFD!","3lD!F","3lDF!","3!lFD","3!lDF","3!FlD","3!FDl","3!DlF","3!DFl","3Fl!D","3FlD!","3F!lD","3F!Dl","3FDl!","3FD!l","3Dl!F","3DlF!","3D!lF","3D!Fl","3DFl!","3DF!l","l3!FD","l3!DF","l3F!D","l3FD!","l3D!F","l3DF!","l!3FD","l!3DF","l!F3D","l!FD3","l!D3F","l!DF3","lF3!D","lF3D!","lF!3D","lF!D3","lFD3!","lFD!3","lD3!F","lD3F!","lD!3F","lD!F3","lDF3!","lDF!3","!3lFD","!3lDF","!3FlD","!3FDl","!3DlF","!3DFl","!l3FD","!l3DF","!lF3D","!lFD3","!lD3F","!lDF3","!F3lD","!F3Dl","!Fl3D","!FlD3","!FD3l","!FDl3","!D3lF","!D3Fl","!Dl3F","!DlF3","!DF3l","!DFl3","F3l!D","F3lD!","F3!lD","F3!Dl","F3Dl!","F3D!l","Fl3!D","Fl3D!","Fl!3D","Fl!D3","FlD3!","FlD!3","F!3lD","F!3Dl","F!l3D","F!lD3","F!D3l","F!Dl3","FD3l!","FD3!l","FDl3!","FDl!3","FD!3l","FD!l3","D3l!F","D3lF!","D3!lF","D3!Fl","D3Fl!","D3F!l","Dl3!F","Dl3F!","Dl!3F","Dl!F3","DlF3!","DlF!3","D!3lF","D!3Fl","D!l3F","D!lF3","D!F3l","D!Fl3","DF3l!","DF3!l","DFl3!","DFl!3","DF!3l","DF!l3"]
for (let index = 0; index < inp.length; index++) {
if (guess(2,inp[index]) == CORRECT){
console.log(inp[index]);
}
}
// Output: F!3lD
Yay!! We found the 2nd part which is F!3lD
Third
export function validate_3(a:int, b:int, c:int, d:int, e:int):int {
var f:int = 0;
if (b * a != 4800) goto B_a;
if (c + a != 178) goto B_a;
if (c + b != 126) goto B_a;
if (d * c != 9126) goto B_a;
if (d - e != 62) goto B_a;
f = c * 4800 - e * d == 367965;
label B_a:
return f;
}
As you can see, a
is the first character of our input, b
is second and so on..
So we need to somehow calculate the correct input?
But I was lazy, I used the z3 sat solver to help me:
from z3 import *
s = Solver()
# Declare 5 Bit Vector
p3 = [BitVec('b%i'%i,16) for i in range(5)]
# Set condition for each character
s.add(p3[0] * p3[1] == 4800)
s.add(p3[2] + p3[0] == 178)
s.add(p3[2] + p3[1] == 126)
s.add(p3[2] * p3[3] == 9126)
s.add(p3[3] - p3[4] == 62)
s.add(p3[2] * 4800 - p3[4] * p3[3] == 367965)
s.check()
model = s.model()
for p in p3:
# Print the result
print(chr(model[p].as_long()),end='')
# d0Nu7
Yay!! We calculate the 3rd part is d0Nu7
Fourth
The fourth part is from the script.js
but it was obfuscated:
function c(b) {
var e = {
'HLPDd': function(g, h) {
return g === h;
},
'tIDVT': function(g, h) {
return g(h);
},
'QIMdf': function(g, h) {
return g - h;
},
'FIzyt': 'int',
'oRXGA': function(g, h) {
return g << h;
},
'AMINk': function(g, h) {
return g & h;
}
}
, f = current_guess;
try {
let g = e['HLPDd'](btoa(e['tIDVT'](intArrayToString, window[b](b[e['QIMdf'](f, 0x26f4 + 0x1014 + -0x3707 * 0x1)], e['FIzyt'])()['toString'](e['oRXGA'](e['AMINk'](f, -0x1a3 * -0x15 + 0x82e * -0x1 + -0x1a2d), 0x124d + -0x1aca + 0x87f))['match'](/.{2}/g)['map'](h=>parseInt(h, f * f)))), 'ZGljZQ==') ? -0x1 * 0x1d45 + 0x2110 + -0x3ca : -0x9 * 0x295 + -0x15 * -0x3 + 0x36 * 0x6d;
} catch {
return 0x1b3c + -0xc9 * 0x2f + -0x19 * -0x63;
}
}
I stuck here for a long time.. The cleaned code looks like this:
intArrayToString(window[b](b[f-1], 'int')()['toString'](f&4 << 2)['match'](/.{2}/g)['map'](h=>parseInt(h, f * f))) === 'dice' ? 1 : 0;
Basically take one character of our input and do some convertion and see if equal to dice
Then I notice it pass our input as attribute of window
because b
is our input
Therefore I decided list all the window’s attributes with 5 characters:
let keys = Object.keys(window)
undefined
for (let index = 0; index < keys.length ; index++) {
if(keys[index].length == 5) console.log(keys[index]);
}
alert
close
fetch
focus
print
quit_
read_
IDBFS
ABORT
ccall
cwrap
_free
HEAP8
abort
Then I brute force one by one, turns out cwrap
is the correct input!
Fifth
export function validate_5(a:int, b:int, c:int, d:int, e:int):int {
var f:int = g_a - 16;
f[15]:byte = a;
f[14]:byte = b;
f[13]:byte = c;
f[12]:byte = d;
f[15]:byte = f[15]:ubyte + 12;
f[14]:byte = f[14]:ubyte + 4;
f[13]:byte = f[13]:ubyte + 6;
f[12]:byte = f[12]:ubyte + 2;
d = 0;
if (f[15]:ubyte != 121) goto B_a;
if (f[14]:ubyte != 68) goto B_a;
if (f[13]:ubyte != 126) goto B_a;
d = e == 77 & f[12]:ubyte == 35;
label B_a:
return d;
}
This was easy, just minus the value and print it:
p5 = bytearray([121-12,68-4,126-6,35-2,77])
print(p5)
# bytearray(b'm@x!M')
Last
export function validate_6(a:int, b:int, c:int, d:int, e:int):int {
var f:int = 0;
if ((b + 2933) * (a + 1763) != 5483743) goto B_a;
f = e == 125 & (d + 1546) * (c + 3913) == 6431119;
label B_a:
return f;
}
Same as the third part, I used Z3 sat solver to get the correct input:
s2 = Solver()
# Declare 5 Bit Vector
p6 = [BitVec('c%i'%i,16) for i in range(5)]
for i in range(5):
# Make sure the input is printable character
s2.add(And(p6[i] <= 126, p6[i] >= 33))
# Set condition for each character
s2.add((p6[1] + 2933) * (p6[0] + 1763) == 5483743)
s2.add(p6[4] == 125)
s2.add((p6[3] + 1546) * (p6[2] + 3913) == 6431119)
s2.check()
model = s2.model()
for p in p6:
# Print the result
print(chr(model[p].as_long()),end='')
# T$r3}
And lastly we get the last part T$r3}
Enter the flag in the website, on the fourth time it say it is the correct flag!!
Flag
dice{F!3lDd0Nu7cwrapm@x!MT$r3}