Holiday Hack 2020: Defeat Fingerprint Sensor
Objective
Terminal - The Elf Code
Challenge
In the top left corner of the Dining Room, Ribb Bonbowford stands next to an arcade game, The Elf Code:
Hello - my name is Ribb Bonbowford. Nice to meet you!
Are you new to programming? It’s a handy skill for anyone in cyber security.
This challenge centers around JavaScript. Take a look at this intro and see how far it gets you!
Ready to move beyond
elf
commands? Don’t be afraid to mix in native JavaScript.Trying to extract only numbers from an array? Have you tried to
filter
?Maybe you need to enumerate an object’s keys and then filter?
Getting hung up on number of lines? Maybe try to minify your code.
Is there a way to
push
array items to the beginning of an array? Hmm…
There’s a series of seven JavaScript-based challenges that involve moving my elf around a grid, avoiding obsticles and answering challenges to progress. Each level is solved with a limited number of Javascript lines.
#1
Program the elf to the end goal in no more than 2 lines of code and no more than 2 elf commands.
elf.moveLeft(10)
elf.moveUp(10)
Completed in 2 elf statements.
#2
Program the elf to the end goal in no more than 5 lines of code and no more than 5 elf command/function execution statements in your code.
To turn off the Yeeter, I’ll need to move to the level, call elf.get_lever(0)
, add two to the returned value, and submit it with elf.pull_lever(result)
.
elf.moveLeft(6)
elf.pull_lever(elf.get_lever(0) + 2)
elf.moveLeft(4)
elf.moveUp(10)
Completed in 5 elf statements.
#3
Program the elf to the end goal in no more than 4 lines of code and no more than 4 elf command/function execution statements in your code.
I need to make sure to get all the lollipops this time. This level introduces the elf.moveTo()
function:
for (i = 0; i < 3; i++) {
elf.moveTo(lollipop[i])
}
elf.moveUp(1)
Complete in 2 elf statements.
#4
Program the elf to the end goal in no more than 7 lines of code and no more than 6 elf command/function execution statements in your code.
for (i = 0; i < 4; i++) {
elf.moveLeft(3);
elf.moveUp(11);
elf.moveLeft(3);
elf.moveDown(11);
}
Completed in 4 elf statements.
#5
Program the elf to the end goal in no more than 10 lines of code and no more than 5 elf command/function execution statements in your code.
The munchkin can be disabled by asking with elf.ask_munch(1)
, taking the returned array, and returning only the elements which are strings.
elf.moveTo(lollipop[1])
elf.moveTo(lollipop[0])
elf.tell_munch(elf.ask_munch(0).filter(elem => typeof elem === 'number'));
elf.moveUp(2)
Completed in 5 elf statements.
#6
Program the elf to the end goal in no more than 15 lines of code and no more than 7 elf command/function execution statements in your code.
To solve the munchkin, I’ll need to get a json object from him, and return the name of the key that holds the value “lollipop”.
for (i = 0; i < 4; i++) {
elf.moveTo(lollipop[i]);
}
elf.moveLeft(8);
elf.moveUp(2);
var from_munch = elf.ask_munch(0);
const keys = Object.keys(from_munch);
for (key of keys) {
if (from_munch[key] == "lollipop") {
elf.tell_munch(key);
}
}
elf.moveUp(2);
Completed in 6 elf statements.
#7 - Bonus
Program the elf to the end goal in no more than 25 lines of code and no more than 10 elf command/function execution statements in your code.
This level is full of challenges to disable. To pull the lever, I just need to elf.pull_lever(#)
while standing on that lever. The munchkin is much more complex. I need to write a function that takes one argument that is an array of arrays, and returns the sum of all numbers in those arrays.
function func(input) {
sum = 0
input.forEach(outer => outer.forEach(inner => {
if (typeof(inner) == 'number') {
sum += inner;
}
}))
return sum;
}
var moves = [elf.moveDown, elf.moveLeft, elf.moveUp, elf.moveRight];
for (i = 0; i < 2; i++) {
for (j = 0; j < 4; j++) {
moves[j](1 + j + (4 * i))
elf.pull_lever(4 * i + j)
}
}
elf.moveUp(2);
elf.moveLeft(4);
elf.tell_munch(func);
elf.moveUp(2)
To make it through the swirl without using too many lines or elf commands, I used an array of move functions and nested for loops.
Completed in 6 elf statements.
#8 - Bonus
Program the elf to the end goal in no more than 40 lines of code and no more than 10 elf command/function execution statements in your code.
For each of the levers, I must submit the sum of the values from all the levers before it.
For the munchkin, I must give it a function that will look through an array of dicts, finding the key of the value lollipop.
function func(input) {
for (var i = 0; i < input.length; i++) {
keys = Object.keys(input[i]);
for (var j = 0; j < keys.length; j++) {
if (input[i][keys[j]] == 'lollipop') {
return keys[j];
}
}
}
}
elf.moveRight(1);
var lever_sums = [0, 0, 0, 0, 0, 0];
lever_sums[0] = elf.get_lever(0);
elf.pull_lever(lever_sums[0]);
move_fns = [elf.moveRight, elf.moveLeft]
for (var i = 1; i < 6; i++) {
elf.moveUp(2);
move_fns[i % 2](2 * i + 1)
lever_sums[i] = lever_sums[i - 1] + elf.get_lever(i)
elf.pull_lever(lever_sums[i])
}
elf.moveUp(2);
elf.tell_munch(func);
elf.moveRight(11);
Completed in 9 elf statements.
Defeat Fingerprint Sensor
Hints
Ribb suggests I might use my JavaScript skills on the Santavator:
Wow - are you a JavaScript developer? Great work!
Hey, you know, you might use your JavaScript and HTTP manipulation skills to take a crack at bypassing the Santavator’s S4.
No badget hints for this one.
Santavator Floor 3
In objective 4, I looked at the JavaScript for the Santavator and how to bypass the S4 stream to enable the buttons. There’s extra security around Santa’s Office, floor 3. When that button is pushed, instead of going there, a fingerprint sensor is revealed:
Clicking on it as a non-Santa player just makes a buzz noise. As Santa, it works and I’m taken to Santa’s Office.
JavaScript
Previous Work from Obj 4
The Santavator panel is run by script loaded from elevator.kringlecastle.com, specifically app.js
:
In Objective 4, I looked at adding the powered
class to each button but changing the checks in the renderTraps
function:
const renderTraps = () => {
TRAPS.forEach((points, index) => {
const fillLevel = pl.Math.clamp(PARTICLE_COUNTS[index].length / trapTargetCounts[index], 0, 1);
const steppa = Math.floor(fillLevel / (1 / wireSteps[index]));
wireElements[index].style.backgroundPosition = `0 ${ -wireElements[index].clientHeight * steppa }px`;
ledElements[index].classList[fillLevel === 1 ? 'add' : 'remove']('on');
powered[index] = fillLevel === 1;
});
btn1.classList[powered[2] ? 'add' : 'remove']('powered');
btn3.classList[powered[2] ? 'add' : 'remove']('powered');
btn2.classList[powered[2] && powered[0] && hasToken('workshop-button') ? 'add' : 'remove']('powered');
btnr.classList[powered[2] && powered[0] ? 'add' : 'remove']('powered');
btn4.classList[powered[2] && powered[1] && powered[0] ? 'add' : 'remove']('powered');
};
Just from this I can tell that btn4
is the floor 3 button, as it’s the only one that requires all three S4 streams to turn on. I can just change that in the Chrome dev tools to:
btn4.classList['add']('powered');
And now that button is powered:
Additional RE
Looking around a bit more, I see where the buttons are set to the btn
variables, and how clicks are handled:
const btn1 = document.querySelector('button[data-floor="1"]');
const btn2 = document.querySelector('button[data-floor="1.5"]');
const btn3 = document.querySelector('button[data-floor="2"]');
const btn4 = document.querySelector('button[data-floor="3"]');
const btnr = document.querySelector('button[data-floor="r"]');
btn1.addEventListener('click', handleBtn);
btn2.addEventListener('click', handleBtn);
btn3.addEventListener('click', handleBtn);
btn4.addEventListener('click', handleBtn4);
btnr.addEventListener('click', handleBtn);
The handle function for btn4
is different than the others. The handleBtn
function gets the floor from the button pushed, and then makes an AJAX web call:
const handleBtn = event => {
const targetFloor = event.currentTarget.attributes['data-floor'].value;
$.ajax({
type: 'POST',
url: POST_URL,
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify({
targetFloor,
id: getParams.id,
}),
success: (res, status) => {
if (res.hash) {
__POST_RESULTS__({
resourceId: getParams.id || '1111',
hash: res.hash,
action: `goToFloor-${targetFloor}`,
});
}
}
});
}
The handleBtn4
function has that same AJAX call, but there are additional checks before:
const handleBtn4 = () => {
const cover = document.querySelector('.print-cover');
cover.classList.add('open');
cover.addEventListener('click', () => {
if (btn4.classList.contains('powered') && hasToken('besanta')) {
$.ajax({
type: 'POST',
url: POST_URL,
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify({
targetFloor: '3',
id: getParams.id,
}),
success: (res, status) => {
if (res.hash) {
__POST_RESULTS__({
resourceId: getParams.id || '1111',
hash: res.hash,
action: 'goToFloor-3',
});
}
}
});
} else {
__SEND_MSG__({
type: 'sfx',
filename: 'error.mp3',
});
}
});
};
First, it add the class open
to the cover
object, and it adds an event listener for a click. When the fingerprint reader is clicked, there’s one more if
before making the AJAX call that takes the player to Santa’s Office:
if (btn4.classList.contains('powered') && hasToken('besanta')) {
hastoken
is a one line function that checks if a string is in the array tokens
that is created when the page loads:
let tokens = (getParams.tokens || '').split(',');
...[snip]...
const hasToken = name => tokens.indexOf(name) !== -1;
Modify
There are many ways to modify this to bypass the check. I can add a line before the check that adds that token to tokens
:
cover.addEventListener('click', () => {
tokens.push('besanta');
if (btn4.classList.contains('powered') && hasToken('besanta')) {
$.ajax({
Or just change the check:
cover.addEventListener('click', () => {
if (btn4.classList.contains('powered')) {
$.ajax({
On running, I find myself in Santa’s Office:
Tinsel Upatree is surprised:
GOSHGOLLY
How did you get in here??
I mean, hey, I’m impressed you made it in here, but you’ve got to leave!
Breaking into Santa’s office might mean immediate membership on the wrong side of the Naughty/Nice List.